您的位置:首页 > 其它

约瑟夫环问题学习小记

2017-10-26 08:03 106 查看

问题的提出

有n个人围成一个圈,按顺时针编号为1到n。现在从编号为1的人开始报1,下一个人报2,如此类推,报到m的人就退出,接着下一个人继续从1开始报。问最后一个剩下的人是谁。

解法1

可以通过链表的形式来模拟这个过程,时间复杂度为O(nm)。

解法2

我们把每个人的编号都-1,也就是编号变为了从0到n-1,显然这两个问题是等价的。

第一个出去的人的编号肯定是(m−1)modn,然后接着从k=mmodn开始报。

那么剩下的人就是k,k+1,...,n−1,0,...,k−3,k−2。

考虑对这些人的编号做一下变换:

k变为0

k+1变为1



n−1变为n−k−1

0变为n−k



k−2变为n−2

k−1变为n−1

不难发现其实就是把i变为i−k。

那么当我们把第k−1个人去掉后,剩下的游戏就等价于一个大小为n−1的约瑟夫环。设其最后答案为x′,大小为n的约瑟夫环的答案为x,不难发现有x=(x′+m)。

通过这种思路不断地递归下去不难发现有如下递推式:

设f[n]表示大小为n的约瑟夫环每次走m步的最终答案。

f[1]=0

f[n]=(f[n−1]+m)modn

至此我们得到了一个比算法1要优秀的O(n)解法。

小拓展

当n很大而m很小的时候,比如说n<=109,m<=106,这时就有一些比较特殊的解法。

当模数逐渐变大的时候,我们发现可能一次可以跳很多步且在这期间实际并不用取模,这时就可以一次性把这些位置都处理掉,从而大大的节约时间。

设当前模数为i,每一步跳k个位置,我们一次性跳t步。

不难得到w+t∗k<i+t−1化简后有t<i−w−1k−1。

那就是说这t步我们是可以一次性处理掉的。

代码

int solve(int n,int k)
{
int i=2,w=0;
while (i<=n)
{
int t=(i-w-1)/(k-1);
if ((i-w-1)%(k-1)==0) t--;
if (i+t>n) {w+=(n-i+1)*k;break;}
w+=t*k;i+=t;
w=(w+k)%i;i++;
}
return w;
}


在维基百科上面还有一种更优秀的解法:



至于如何证明,这个坑还是留着以后再填吧。据说 具体数学 里面有关于这条式子的证明。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: