您的位置:首页 > 其它

圆圈报数最后剩下的数 约瑟夫环问题 Joseph

2013-09-15 22:35 309 查看
问题:有n个数,围成一个圈。现在从某一个数开始顺时针报数(第一个报1,依次报数),当报到m时,将报数者删除。并从下一个数开始重新报数。问,最后一个留下的数是谁?

假设这n个数依次是0,1,2...n-1。

问题源于:高德纳的《具体数学》。9oj编号1356。剑指45。

思路:

本题最直观的思路是按照题目说的一步一步做,O(n*m)级别的。当数字n很大的时候,变得非常慢。

因此下面这种毫无算法的笨方法是不可取的。

int main()
{
int n,m;
int dex;
char a[1000000];
while(scanf("%d", &n)!=EOF)
{
if(n==0)
return 1;
scanf("%d", &m);
for(int i=0;i<n;i++)
a[i] = 0;
dex = 0;
for(int i=0; i<n; i++)
{
//开始报数
int j=0;
while(j<m)
{
if(a[dex]==0)
{
j++;
}
dex = (dex+1)%n;
}
dex = (dex-1+n)%n;
a[dex] = 1;
//printf("%d  ",dex);
dex = (dex+1)%n;
}
printf("%d\n", (dex-1+n)%n+1);
}
return 1;
}

好的思路是数学算法:

如果每一轮报数之后,都将编号重新映射为从0开始。那么会发现该问题是存在类似子问题的。

设f(n, m)表示n个数每次报数m的情况下最后留下的数。

问题(n, m)和问题(n-1, m)有什么关系?

问题(n, m)的初始环形为:

0  1  2  3  4 .... n-2  n-1

问题(n-1, m)的初始环形为:

0  1  2  3  4 .... n-2

注意到,问题(n, m)当第一轮报数结束的时候,所面临的问题不是(n-1, m),需要作位置偏移。

问题(n, m)的初始状态为:

0  1  2  3  4 .... n-1  

第一轮报数结束后,数(m-1)%n被删去,此时状态变为:

0  1  2  3 ... (m-1)%n-1  (m-1)%n+1 ... n-1

其实是状态这样的,

(m-1)%n+1 ... n-1  0  1  2  3 ... (m-1)%n-1

现在希望其变成,因为这才是问题(n-1, m)

0  1  2  3  .... n-2

反过来想:假如我已经求得了f(n-1, m),想求f(n, m),那么我只需要将当前的问题(n-1, m)的状态转变为问题(n, m)第一轮报数结束时的状态即可,这样就能将f(n-1, m)变为f(n, m)。

即,将0  1  2  3  .... n-2 变为(m-1)%n+1 ... n-1  0
 1  2  3 ... (m-1)%n-1

依次的,把0放到(m-1)%n+1的位置,把1放到(m-1)%n+2的位置……

对于i,要变为(i+(m-1)%n+1)%n= (i+m)%n

因此,对于问题(n-1, m)中的最后遗留数f(n-1, m),它会被转换为(f(n-1, m) + m)%n,这是问题(n, m)的最后遗留数。

由此得到递归公式 f(n-1, m) = (f(n-1, m) + m)%n

初始条件为 f(1, m) = 0

# include<stdio.h>

int lastremain(unsigned int n, unsigned int m)
{
if(n < 1 || m < 1)
return -1;
int last = 0;
for(int i=2;i<=n;i++)
last = (last+m)%i;
return last+1;
}

int main()
{
int n,m;
while(scanf("%d", &n)!=EOF)
{
if(n==0)
return 1;
scanf("%d", &m);
printf("%d\n", lastremain(n,m));
}
return 1;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: