约瑟夫环问题(数论)



约瑟夫环问题描述:

n个人围成一圈(编号分别为1-n),从某人开始顺序报号1,2,3…m凡报到m者的人出列,再接着从下一个人开始数,输出最终出列的人的编号。

(约瑟夫环是一个数学的应用问题:已知n个人(以编号123...n分别表示)围坐在一张圆桌周围。从编号为k的人开始报数,数到m的那个人出列;他的下一个人又从1开始报数,数到m的那个人又出列;依此规律重复下去,直到圆桌周围的人全部出列。)

Input

输入总人数n,接着输入一个整数km,前者表示从第k人开始数,后者表示每次数到m的人出列,(n,k,m之间均用空格隔开,  1 <= k <= n <= 10000)

Sample Input

5 2 3
7 1 3


Sample Output

5
4

第一种方法:模拟出环过程

代码:#include<iostream>
using namespace std;
int main()
{
 int N;//人的总个数
 int M;//间隔多少个人

 cin>>N;
 cin>>M;

 bool *p=new bool[N+1];//[1……N]为true表示此人还未出环
 for (int i=1; i <= N; i++)
  *(p+i)=true;
 
 int count=0;//统计出环的人数

 for (int i=1, j=0; ;i++)//i用来表示循环,j用来计算是不是第N个人
 {
  if (*(p+i))//此人还未出环
  {
   j++;
   if (j == M)
   {
    *(p+i)=false;
    j=0;
    count++;//统计出环的人
   }
   if (count == N)
   {
    cout<<"最后出环的人是:"<<i<<endl;
    break;
   }
  }

  if(i == N)
   i=0;
 }

 delete []p;
 
 return 0;
}

上述方法的效率很低,其时间复杂度为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) 

第二种方法:根据以上推出的结论

AC代码:

#include<bits/stdc++.h>


using namespace std;


int solution(int n, int m) 

    if(n < 1 || m < 1) 
       return -1; 
 
    vector<int> f(n+1,0);

    f[1]=0;

  
   for(unsigned i = 2; i <= n; i++) 
       f[i] = (f[i-1] + m) % i;  


   return f[n]; 

int main(void)
{
 int n,m,t;//n表示总人数,m表示间隔人数,t表示起始人的序号
 cin>>n>>m>>t;
 cout<<(solution(n,m)+t-1)%n+1<<endl;//直接输出
 return 0;
 }



转载于:https://www.cnblogs.com/flyawayl/p/8305621.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值