首页 > 代码库 > 约瑟夫环——POJ3379

约瑟夫环——POJ3379

题目描述:

给出一个长度是n的字符串环,每次搁k个加入字符串中对应位置的字母序的下一个字母,执行m次,问最后一次插入的是什么字母。

大致思路:

正着想的话只能用模拟的方法解决,但是m有10^9这么大,而把问题倒过来想一下的话,那就变成了给出一个n+m的字符串每次搁k个字符删掉一个,最后剩下一个长度为n的字符串,问起始位置是什么字母。这样的话就变成了约瑟夫问题,约瑟夫环问题可以在不用考虑内容的情况下计算出最后剩下元素的位置。又因为字符串是一个环,所以可以假定开始的位置就是1,最后操作结束的位置就是最后一个元素对应的位置。这样的过程就只需要记录一下这个位置被去掉了多少次即可。

过程跟约瑟夫环的过程类似,利用公式f(i) = (f(i-1)+m) % i。而如果利用这个公式递推的话时间复杂度还是O(m)的,所以利用一个小条件,k<=10000,所以这样的话每次就可以删除y=(m-x)/(k+1)+1个结点了。然后利用公式,下一次开始的位置就是x+y*(k+1),需要注意的是,如果更新过后的x并没有超过m的话那么x是需要减掉y的,因为去掉了y个结点,所以编号要减掉y,而如果超过m的话,那么就不用考虑去掉的元素,因为去掉的元素编号一定在现在位置的后面,这样只需要把x模m就可以了,之后根据公式,将得到的x模(m-y),更新m。一直进行这样的过程,把n+m减到n停止,过程中因为是从1开始计数的,所以只需要统计一下过程中有多少次操作位置是1即可。最后把统计的结果和位置代入字符串中,输出对应字母即可。

代码:

#include <iostream>
#include <cstring>
#include <cstdio>

using namespace std;

const int maxn = 10000 + 10;
int n,m,k;
char a[maxn];

int main() {
    while (scanf("%d%d%d",&n,&k,&m) != EOF) {
        scanf("%s",a+1);
        m += n;
        int sum = 0;
        int x = 1;
        while (m > n) {
            if (x == 1) sum++;
            int s = min(m-n,(m-x)/(k+1)+1);
            x += s*(k+1);
            if (x > m) x -= m;
            else x -= s;
            m -= s;
            x %= m;
            if (!x) x = m;
        }
        sum %= 26;
        a[x] = (a[x] - 'A' + sum) % 26 + 'A';
        cout<<a[x]<<endl;
    }
}


约瑟夫环——POJ3379