N 个人围成一圈,从第一个人开始报数,数到 M的人出圈;再由下一个人开始报数,数到 M的人出圈。
约瑟夫问题的两种形式:
1.求出最后剩下的人的原始序号:
#includeint 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.依次打印每次出列的人的原始序号:
#includeint 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人)
新序号:345×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)退出后,序号变化如下:
旧序号:345 x0 1 2(6人)
新序号:x 0 1 x2 3 4(5人)
可以发现,旧序号=(新序号+4)%6
第3轮报数第4个人(编号还是3)退出后,序号变化如下:
旧序号:x 0 1 x2 3 4(5人)
新序号:x 12x 3×0(4人)
可以发现,旧序号=(新序号+4)%5
……
第6轮报数第4个人(编号为1)退出后,序号变化如下:
旧序号:x 0 1 x xxx (2人)
新序号:x0 xx xx 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