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

问题:有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;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值