约瑟夫环问题
约瑟夫环问题的来源:
约瑟夫环问题(Josephus)问题是由古罗马史学家约瑟夫(Flavius Josephus)提出的,传说在公元66-70年的犹太人反抗古罗马起义的战争中,约瑟夫作为一名将军,想方设法守住了裘达伯特城长达47天之久,在城市沦陷之后,他和40名死硬的将士在附近的一个洞穴中避难。在洞穴里,这些将士坚持地表决说“要投降毋宁死”。于是,约瑟夫建议每隔两个人杀死一人,而这个顺序是由抽签决定的。约瑟夫有预谋地抓到了最后一签,并且,作为洞穴中的两个幸存者之一,他说服了另一个幸存者一起投降了罗马。
约瑟夫问题的规则如下:
(1)n个人围成一个约瑟夫环;
(2)顺时针报数,每次报到m的人将会被淘汰,离开这个约瑟夫环;
(3) 剩下的人又重新组成一个新的约瑟夫环;
(4)然后从被淘汰的下一个人开始重新报数,报到m的人继续被淘汰,离开这个约瑟夫环,以此类推,直到剩余一人。
你非常不幸地参加了这场“游戏”,当然,你是想获胜的,所以你必须快速决定要站到哪一个位置,才能使得最后获胜的人是你。
正常想法:
如何知道n个人中最后剩下的人是谁,如果利用程序实现的话,很多人首先会想到这个圆圈其实就是循环队列,每次数到m的人弹出这个队列,然后剩下的人又重新组成一个新的循环队列,但是这种做法的时间复杂度是O(nm),如果n和m很大的话,这种做法很显然就会超时。
正确想法:
我们要利用约瑟夫问题的性质,即每次数到m的人离开这个约瑟夫环,问题就转化成n-1的约瑟夫环问题了,即m+1...n和1...m-1组成一个新的n-1的约瑟夫环,问题就转化为求n-1的约瑟夫问题了,然后n-1的约瑟夫环问题其实可以转化成人数更少的约瑟夫环问题了,最后人数变成1的时候即是答案了。
认真地分析过这个过程后,其实有点发现这个过程有点像递归,但是这个问题如果用递归去实现的话比较麻烦,然而一些递归问题是可以用递推去实现的,这一题也不例外。
递推的实现方法其实就是,为了方便我们进行运算,我们将n个人进行编号,记为0到n-1,这是为了方便后面进行的膜运算,当n为1时,答案自然就是编号为0的人,当n为2时,编号为(m-1)%n的人将被淘汰,剩下的人就是答案,拓展开来,当人数n为任意一个自然数时,第一轮编号为(m-1)%n的人一样被淘汰,然后将编号为(m-1)%n这个人调到约瑟夫环的最前面,编号为(m-1)%n前面的人按顺序调到最后面。
举个例子,当n为5,m为3时,0 1 2 3 4,第一轮编号为2的人淘汰,剩下的人排列顺序就变成这样:3 4 0 1,可以发现最后剩下的人一定在这4个人当中,所以问题就转化为n为4,m为3的约瑟夫环问题,而n为4,m为3的约瑟夫环问题很明显是0 1 2 3的答案,记为a,然后上面3 4 0 1约瑟夫环问题的答案就为:ans=(a+m)%(n-1),以此类推,答案就很显就出来了。
值得注意的是,最后的答案是第几个人,所以要在编号上加一。
标准程序:
#include <cstdio>
int n,m,ans;
int main()
{
scanf("%d%d",&n,&m);
for (int i=2;i<=n;i++) ans=(ans+m)%i;
printf("%d\n",ans+1);
}