约瑟夫问题

Time Limit:
1000ms
Memory limit:
65536kB
题目描述
约瑟夫问题:有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。

输入
每行是用空格分开的两个整数,第一个是 n, 第二个是 m ( 0 < m,n <=300)。最后一行是:

0 0

输出
对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号
样例输入
6 2
12 4
8 3
0 0
样例输出
5
1
7
解答:
#include <iostream>
using namespace std;
bool a[301];
int main()
{
 int m,n;
 while(cin>>m>>n,n || m)
 {
   memset(a,0,sizeof(a));
   int i,j,c;
   i=c=0;
   while(c<m-1)//有m只猴子,要进行m-1次标记
   {
   j=0;//每一次标记都要重新置0,进行n次
      while(j<n)
   {
     i=(i+1)%m;
     if(a[i]==0)j++;   
   } 
   a[i]=1;
   c++; 
   } 
   for(c=0;c<m;c++)
   {
  if(a[c]==0)
  {
    if(c==0) cout<<m<<endl;
//这里使用了一个小技巧,将最后一个猴子的编号作为数组下标的0,这样后面的编号就跟下标相同了
    else cout<<c<<endl;
    break;
  } 
   }
 }
    system("pause");
    return 0;
}
另解:(来自于网络)

约瑟夫环问题是一道经典的数据结构题目
问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。求胜利者的编号。
一般我们采用一个循环队列来模拟约瑟夫环的求解过程,但是如果n比较大的时候,采用模拟的方式求解,需要大量的时间来模拟退出的过程,而且由于需要占用大量的内存空间来模拟队列中的n个人,并不是一个很好的解法。
在大部分情况下,我们仅仅需要知道最后那个人的编号,而不是要来模拟一个这样的过程,在这种情况下,可以考虑是否存在着一种数学公式能够直接求出最后那个人的编号。

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始):
我们先看第一个人出列后的情况,显而易见,第一个出列的人的编号一定是m%n-1,这个人出列后,剩下的n-1个人组成了一个新的约瑟夫环,这个约瑟夫环的第一个人在最开始的环中的编号是k=m%n(就是第一个出列的人的下一个)
k  k+1  k+2  ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。
事实上,可以把这个环又映射成为一个新的环:
k  --- 0
k+1 --- 1
k+2 --- 2
...  ....
k-2 -- n-1
可以看出,这就是原问题中把n替换成n-1的情况,假设我们已经求出来在这种情况下最后胜利的那个人的编号是x,那个倒推回去的那个人的编号就正好是我们要求的答案,显而易见,这个编号应该是(x+k)%n
那么如何知道n-1个人下面的这个x呢,yes,就是n-2个人情况下得到的x'倒推回去,那么如何知道n-2情况下的x'呢,当然是求n-3个人,这就是一个递归的过程
f(1) = 0(f(1)就是现在还剩下1个人,那么无论m为几,这个人总会出列,因此f(1)=0)
f(n) = (f(n-1)+m)%n
那么我们要求f(n),就从f(1)倒推回去即可
int f(int n, int m)
{
   int r = 0;
   for(int i = 2; i <= n; i++)
       r = (r + m) % i;
   return r + 1; //这是因为日常生活中编号总是从1开始
}
这种方法比模拟的方法快多了,我们在碰到问题的时候,可以想一想是否有数学公式来求解

posted on 2010-04-10 21:11  蓝牙  阅读(2241)  评论(0编辑  收藏  举报