约瑟夫问题(c语言)

摘自百度百科

         N 个人围成一圈,从第一个人开始报数,数到 M 的人出圈;再由下一个人开始报数,数到 M 的人出圈。

约瑟夫问题的两种形式:

1.求出最后剩下的人的原始序号:

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==

#include<stdio.h>
int main()
{
	int m,n,k,b,f;
	scanf("%d%d",&m,&n);
		b = n-1;//n-1使得序号从0开始 
		for(k=1;k<=m;k++)
		{
			f = b%k;
			b = f+n;
		}
		printf("%d ",f+1);//f+1使得序号从1开始 
	return 0;
}

2.依次打印每次出列的人的原始序号:

#include<stdio.h>
int main()
{
	int m,n,i,k,b,f;
	scanf("%d%d",&m,&n);
	for(i=0;i<m;i++)
	{
		b = n-1;//n-1使得序号从0开始 
		for(k=m-i;k<=m;k++)
		{
			f = b%k;
			b = f+n;
		}
		printf("%d ",f+1);//f+1使得序号从1开始 
	}
}

         一开始接触这类题目,我们较直接的思路是用链表或者数组来模拟整个游戏过程,但这有一个缺点是,程序繁琐。为此我们在数学上推敲一下,寻找一些规律。

        因为(编号0~(n-1))报数报到(M-1)的人退出,剩下的人继续从0开始报数,意思就是把原来的序号减去M。比如N=7,M=4 的时候:

第一轮报数第4个人(编号为3)退出,剩下的人的序号都减4变成从0开始:

旧序号:0 1 2 3 4 5 6      (7人)   

新序号:3 4 5 x 0 1 2      (6人)  

可以发现,旧序号=(新序号+4)%7   

注:(因为新=旧-4,所以旧=新+4,又因为他们排成一个圆(0-6-0),且最大的序号只能是6,为防止+4之后序号可能>6,所以要对7取余,比如当+4=7、8、9,取余之后就是0、1、2,这里取余的方法感觉挺巧妙的)

第2轮报数第4个人(编号还是3)退出后,序号变化如下:

旧序号:3 4 5 x 0 1 2       (6人)  

新序号:x 0 1 x 2 3 4        (5人)

可以发现,旧序号=(新序号+4)%6

第3轮报数第4个人(编号还是3)退出后,序号变化如下:

旧序号:x 0 1 x 2 3 4        (5人)  

新序号:x 1 2 x 3 x 0        (4人)

可以发现,旧序号=(新序号+4)%5

……

第6轮报数第4个人(编号为1)退出后,序号变化如下:

旧序号:x 0 1 x x x x       (2人)  

新序号:x 0 x x x x x        (1人)

可以发现,旧序号=(新序号+4)%2 

所以最后一个人的序号一定是0,因为只剩他一个人了,他在前1轮的序号是(0+4)%2=0,在前2轮的序号是((0+4)%2+4)%3=1……所以递推回来,他最原始的序号就是((((((0+4)%2+4)%3+4)%4+4)%5+4)%6+4)%7=1,所以第一题用一个for循环就可以求解了。

        第2题跟第1题思路差不多。首先我们知道这个游戏是每一轮报数出列一人,倒数第二个出列的人是在倒数第二轮出列,倒数第三个出列的人是在倒数第三轮出列……只要我们知道这些人在他们所出列的轮数时的序号,我们就可以按照第1题的方法逆推回去,得到他最原始的序号。(假设N=7,M=4)因为每次都是报数为3的人出列,所以他们出列时的序号一般都是3,有个问题是,当人数减到<M时,序号就不是3了,我们仍然可以通过取余解决,即3%N'(N'为当剩余人数),比如倒数第2个出列的人的原始序号可以由以下表达式求得:

(((((3%2+4)%3+4)%4+4)%5+4)%6+4)%7=2

倒数第3个出列的人的原始序号可以由以下表达式求得:

((((3%3+4)%4+4)%5+4)%6+4)%7=6

……

所以第二题用两个for循环就可以求解,并且没有用到数组。如果要求编号从1开始,只需要把最终结果加上1即可。可以用下面的输入验证一下。

Sample

Input


8 5

Output


5 2 8 7 1 4 6 3

 

评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值