• 约瑟夫环


    虽然这是一个最最基础不过的问题,但是基础也恰恰应该是我们能不费力气,信手拈来的东西。所以好好记一下这道题的做法还是很有必要的。

    问题描述是说给你一个数n,让你从1开始到n报数,报到m的那个数字删掉,然后从后面再从1开始数,遇到数组结尾就从头开始继续往后,依次类推,问最后剩下的那个数字的原始编号是多少。

    这个题最基本的解法就是模拟整个操作过程,然后输出最后剩下的那个数字即可。

    代码如下:

     1 #include<iostream>  
     2 using namespace std;  
     3 int main()  
     4 {  
     5     int N;//人的总个数  
     6     int M;//间隔多少个人  
     7   
     8     cin>>N;  
     9     cin>>M;  
    10   
    11     bool *p=new bool[N+1];//[1……N]为true表示此人还活着  
    12     for (int i=1; i <= N; i++)  
    13         *(p+i)=true;  
    14       
    15     int count=0;//统计自杀的人数  
    16   
    17     for (int i=1, j=0; ;i++)//i用来表示循环,j用来计算是不是第N个人  
    18     {  
    19         if (*(p+i))//此人还活着  
    20         {  
    21             j++;  
    22             if (j == M)  
    23             {  
    24                 *(p+i)=false;  
    25                 j=0;  
    26                 count++;//统计自杀的人  
    27             }  
    28             if (count == N)  
    29             {  
    30                 cout<<"最后自杀的人是:"<<i<<endl;  
    31                 break;  
    32             }  
    33         }  
    34   
    35         if(i == N)  
    36             i=0;  
    37     }  
    38   
    39     delete []p;  
    40       
    41     return 0;  
    42 }  

      上述方法的效率很低,其时间复杂度为O(mn)。当n和m很大时,很难在短时间内得出结果。不过好处就是可以给出n个人出圈的次序。只要在删除前保存一下即可。

           下面利用数学推导,如果能得出一个通式,就可以利用递归、循环等手段解决。下面给出推导的过程:

            (1)第一个被删除的数为 (m - 1) % n。

            (2)假设第二轮的开始数字为k,那么这n - 1个数构成的约瑟夫环为k, k + 1, k + 2, k +3, .....,k - 3, k - 2。做一个简单的映射。

                 k         ----->  0 
                 k+1    ------> 1 
                 k+2    ------> 2 
                   ... 
                   ... 

                 k-2    ------>  n-2 

            这是一个n -1个人的问题,如果能从n - 1个人问题的解推出 n 个人问题的解,从而得到一个递推公式,那么问题就解决了。假如我们已经知道了n -1个人时,最后胜利者的编号为x,利用映射关系逆推,就可以得出n个人时,胜利者的编号为 (x + k) % n。其中k等于m % n。代入(x + k) % n  <=>  (x + (m % n))%n <=> (x%n + (m%n)%n)%n <=> (x%n+m%n)%n <=> (x+m)%n

            (3)第二个被删除的数为(m - 1) % (n - 1)。

            (4)假设第三轮的开始数字为o,那么这n - 2个数构成的约瑟夫环为o, o + 1, o + 2,......o - 3, o - 2.。继续做映射。

                 o         ----->  0 
                 o+1    ------> 1 
                 o+2    ------> 2 
                   ... 
                   ... 

                 o-2     ------>  n-3 

             这是一个n - 2个人的问题。假设最后的胜利者为y,那么n -1个人时,胜利者为 (y + o) % (n -1 ),其中o等于m % (n -1 )。代入可得 (y+m) % (n-1)

             要得到n - 1个人问题的解,只需得到n - 2个人问题的解,倒推下去。只有一个人时,胜利者就是编号0。下面给出递推式:

              f [1] = 0; 
              f [ i ] = ( f [i -1] + m) % i; (i>1) 

            有了递推公式,实现就非常简单了。

    代码如下:

     1 #include<iostream>  
     2 using namespace std;  
     3 int main()  
     4 {  
     5     int N;//人的总个数  
     6     int M;//间隔多少个人  
     7   
     8     cin>>N;  
     9     cin>>M;  
    10     int result=0;//N=1情况  
    11     for (int i=2; i<=N; i++)  
    12     {  
    13         result=(result+M)%i;  
    14     }  
    15     cout<<"最后自杀的人是:"<<result+1<<endl;//result要加1  
    16     return 0;  
    17 }  
  • 相关阅读:
    全选和选项交互
    无法将类型为excel.applicationclass的com 强制转换为接口类型的解决方法[转]
    SilverLight搭建WCF聊天室详细过程[转]
    进程与线程的一个简单解释
    Visiual Studio CLR20r3
    C#注册表操作类--完整优化版
    cmd注册外部命令
    C# Dsofile.dll无法注册运行问题解决
    .net版,微信免充值代金卷业务开通验收代码
    C#十进制与任意进制的转换
  • 原文地址:https://www.cnblogs.com/SarahLiu/p/5948250.html
Copyright © 2020-2023  润新知