【数论】约瑟夫问题

约瑟夫问题

    约瑟夫问题是一类经典的又非常简单的基础数论问题

    题目大意(选班长):

         N个人围成一个圈,依次编号为0..N-1。然后随机抽选一个数K,并0号候选人开始按从1到K的顺序依次报数,N-1号候选人报数之后,又再次从0开始。当有人报到K时,这个人被淘汰,从圈里出去。下一个人从1开始重新报数。最后一个人即是班长。

    换成示意图即是这样:

    设N=5,K=3

    1:从0开始报数,报到K,也就是2,2退出

    

    2:从3开始报数,遇到4返回0,0退出

    

    3:从1开始报数,报到4,4退出

    

    4:从1开始报数,报到3然后报回1,1退出,则3为班长

    

那么很容易想到朴素的递推算法:

f[1]=0;
for(int i=2;i<=N;i++) f[i]=(f[i-1]+K)%i;
cout<<f[N];

f[i]表示当有i个人时最后班长的编号。

    那么我们考虑多添加一个人对最后结果的影响

    最终肯定是多添加了一个元素导致整体K前移,而偏移的是K,所以要加回来。

我们可以发现,这种算法在普通时是可以使用的,但是如果N增大,时间、空间都会炸

                              (部分参照hihocoder)

我们假定一个初始序列:

0 1 2 3 4 5 6 7 8 9

当7号进行过报数之后:

0 1 2 - 4 5 6 - 8 9

在这里一轮报数当中,有两名候选人退出了,退出的候选人数量为N/K

由于此时起点为8,则等价于:

2 3 4 - 5 6 7 - 0 1

因此我们仍然可以从f[8]的结果来推导出f[10]的结果。

但需要注意的是,此时f[10]的结果并不一定直接等于(f[8] + 8) mod 10。

这是因为在序列(2 3 4 - 5 6 7 - 0 1)中,数字并不是连续的。

因此我们需要根据f[8]的值进行分类讨论。假设f[8]=s,则根据s和N mod K的大小关系有两种情况:

 

1) s < N mod K : s' = s - N mod K + N
2) s ≥ N mod K : s' = s - N mod K + (s - N mod K) / (K - 1)

由于我们不断的在减小N的规模,最后一定会将N减少到小于K,此时N/K=0。

因此当N小于K时,就只能采用第一种递推的算法来计算了。

下面给出详细代码:(回家拿题乱写了一发,然后貌似就A了……)

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
inline int read(){
    int x=0,f=1;char c=getchar();
    for(;!isdigit(c);c=getchar()) if(c=='-') f=-1;
    for(;isdigit(c);c=getchar()) x=x*10+c-'0';
    return x*f;
}
int Josen(int N,int K){
    int ret=0;
    if(N==1) return 0;//如果N=1返回计算出的结果,避免无限递归
    if(N<K){//迫不得已采用朴素算法求解
        for(int i=2;i<=N;i++) ret=(ret+K)%i;
        return ret;
    }
    else{
        ret=Josen(N-N/K,K);//缩小问题规模
        if(ret<N%K) ret=ret-N%K+N;//分类讨论
        else ret=ret-N%K+(ret-N%K)/(K-1);
        return ret;
    }
}
int T;
int N,K;
int main(){
    T=read();
    while(T--){
        N=read(),K=read();
        cout<<Josen(N,K)<<endl;
    }
}

  

 

原文地址:https://www.cnblogs.com/wxjor/p/6165753.html