约瑟夫问题是个著名的问题:N个人围成一圈,第一个人从1开始报数,报到k的人将被杀掉,接着下一个人又从1开始报,直到最后剩下一个,求最后留下的人的下标。
题目集合
解法1:暴力
可以直接暴力求解,时间复杂度为O(nk)
解法2:递推
设$f(n,k)$为当n个人围成一圈时,最后留下的人的下标。 对于$f(n-1,k)$来说,其结果相当于$f(n,k)$的结果向前移动$k\%(n-1)$位。
因为对于$f(n,k)$来说,去掉第一轮报的数$(k\%n)$后,现在就只剩下$n-1$个数,并且是以$(k\%(n-1)+1)$作为第一个数,即所有数向前移动$k\%(n-1)$位。现在的结果就为$f(n-1,k)$
对于f(5,3)来说,其结果为4。
当其去掉第一轮报的数后,其向前移动了$(3\%4)$位,以4为起始,$f(4,3)$结果为1,对应着$f(5,3)$的结果4向前移动了3位
所以反过来看即为,即为$f(n-1,k)$的结果向后移动$k\%(n-1)$位
即$f(n+1,k)=(f(n,k)+k\%n)\%n$ (x下标从0开始,因为取模结果为[0,n-1])
时间复杂度为$O(n)$

ll josephus2(ll n,ll k) { ll pos=0; for(int len=1;len<=n;len++) { pos = (pos+k)%len; } return pos+1; }
解法3:
如果当前这一位人没被杀掉,则他可以放在幸存者的末尾,直到幸存者数量为1
所以对于下标为i的人,如果在他前面已经被杀掉了q个人,那么他的新的下标为$n+q(k-1)+x,(1\leq x <k)$
如下图所示,最后被淘汰的编号一定是$n*k$,所以幸存者最后的编号是$n*k$
我们现在需要从幸存者最后的编号中恢复出最初编号
假设幸存者这一次的编号为$pos_{i}$,在他后面包括他还有$x$位幸存者,则$[pos_{i-1},pos_{i})$间一定有x个不能被k整除的数
这样才能使在他后面包括他还有$x$位幸存者。所以根据这一推论,我们即可恢复最初的幸存者位置。

ll josephus(ll n,ll k) { ll ans = n * k; ll cnt = 1; // 当前位置r与上一个位置l,[l,r)中有多少个不能被k整除的数 ll tmp; while (ans>n) { ll t1 = cnt/(k-1); ll t2 = cnt%(k-1); if(ans%k>t2) { tmp = ans-(k*t1)-t2; } else { ll res = ans%k+(k-1)-t2; tmp = (ans-ans%k)-(k*(t1+1))+res; } cnt = cnt+ceil((ans-1)/k)-ceil((tmp-1)/k); ans = tmp; } return ans; }
时间复杂度不好算,反正挺快的,大概是$O(logn)$的几十倍,参考链接写的是$O(logn)$,orz。