约瑟夫环问题学习小记

来源:互联网 发布:淘宝助理官方下载免费 编辑:程序博客网 时间:2024/06/05 18:35

问题的提出

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

解法1

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

解法2

我们把每个人的编号都-1,也就是编号变为了从0到n-1,显然这两个问题是等价的。
第一个出去的人的编号肯定是(m1)modn,然后接着从k=mmodn开始报。
那么剩下的人就是k,k+1,...,n1,0,...,k3,k2
考虑对这些人的编号做一下变换:
k0
k+11

n1nk1
0nk

k2n2
k1n1
不难发现其实就是把i变为ik
那么当我们把第k1个人去掉后,剩下的游戏就等价于一个大小为n1的约瑟夫环。设其最后答案为x,大小为n的约瑟夫环的答案为x,不难发现有x=(x+m)
通过这种思路不断地递归下去不难发现有如下递推式:
f[n]表示大小为n的约瑟夫环每次走m步的最终答案。
f[1]=0
f[n]=(f[n1]+m)modn
至此我们得到了一个比算法1要优秀的O(n)解法。

小拓展

当n很大而m很小的时候,比如说n<=109,m<=106,这时就有一些比较特殊的解法。
当模数逐渐变大的时候,我们发现可能一次可以跳很多步且在这期间实际并不用取模,这时就可以一次性把这些位置都处理掉,从而大大的节约时间。
设当前模数为i,每一步跳k个位置,我们一次性跳t步。
不难得到w+tk<i+t1化简后有t<iw1k1
那就是说这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;}

在维基百科上面还有一种更优秀的解法:
这里写图片描述
至于如何证明,这个坑还是留着以后再填吧。据说 具体数学 里面有关于这条式子的证明。

原创粉丝点击