zoukankan      html  css  js  c++  java
  • 【数论】约瑟夫问题

    约瑟夫问题

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

        题目大意(选班长):

             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;
        }
    }
    

      

     

  • 相关阅读:
    牛客(4) 重建二叉树
    牛客(3)从尾到头打印链表
    牛客(2)字符串替换
    牛客(1)二分查找
    同义词+序列+视图+临时表
    用户+授权
    控制文件+日志文件
    oracle表的基本操作
    Linux(CentOS6.8)配置Redis
    Linux(CentOS6.8)配置ActiveMQ
  • 原文地址:https://www.cnblogs.com/wxjor/p/6165753.html
Copyright © 2011-2022 走看看