约瑟夫问题
全都是抄具体数学的
问题\(1\)
有\(n\)个人围成一圈,顺时针从\(1\)到\(n\)编号,并从\(1\)开始报数,如果一个人报的数字是\(2\)的倍数,就把他撒了,后面的人继续,问最终活下来的人的编号
\(Sol1\)
记\(J(n)\)为最终活下来人的编号,有\(J(1)=1\)
如果有\(2n\)个人,那么第一圈过去之后所有偶数编号的人都被撒了,那么可以看成还有\(n\)个人,只不过第\(i\)个人的编号是\(2i-1\),所以有\(J(2n)=2J(n)-1\)
如果有\(2n+1\)个人,那么第一圈过去之后所有偶数编号的人都被撒了,下一圈一开始\(1\)号被撒了,那么可以看成还有\(n\)个人,第\(i\)个人的编号是\(2i+1\),所以有\(J(2n)=2J(n)+1\)
如果我们记\(k\)为\(n\)的二进制最高位,有\(J(n)=2(n-2^k)+1\)打表证明即可,归纳证明即可
问题\(2\)
有\(n\)个人围成一圈,顺时针从\(1\)到\(n\)编号,并从\(1\)开始报数,如果一个人报的数字是\(3\)的倍数,就把他撒了,后面的人继续,问最终活下来的人的编号
\(Sol2\)
如果还是按上面的递归式的方法不用做了
考虑一种新的方法,\(1\)号报完之后,我们给他一个新的编号\(n+1\),\(2\)号报完之后变成\(n+2\),\(3\)号报完就被撒了,\(4\)号报完变成\(n+3\)……同理,每一次令\(3k+1\)和\(3k+2\)的人变成\(n+2k+1\)和\(n+2k+2\)
这样的话第\(k\)个死的人编号是\(3k\),最终活下来的人编号就是\(3n\)了,我们只要知道他原来的编号就行了
对于编号为\(p\)的人,如果\(p>n\),那么\(p=n+2k+v\),其中\(v=1\)或\(2\),所以有\(k=\lfloor{p-n-1\over 2}\rfloor\),而它之前的号码是\(3k+v\),即\(3k+(p-n-2k)=p+k-n\),那么我们可以写出一个代码
inline int calc(R int n){
R int p=3*n;
while(p>n)p=((p-n-1)>>1)+p-n;
return p;
}
关于复杂度的证明,它和下面这种方法等价
如果我们记\(d=3n+1-p\),那么递归过程就变成了
代码如下
inline int calc(R int n){
R int d=1;
while(d<=(n<<1))d=ceil(1.5*d);
return 3*n+1-d;
}
由于这两种方法本质一样,所以复杂度都是\(O(\log n)\)
问题\(3\)
有\(n\)个人围成一圈,顺时针从\(1\)到\(n\)编号,并从\(1\)开始报数,如果一个人报的数字是\(q\)的倍数,就把他撒了,后面的人继续,问最终活下来的人的编号
\(Sol3\)
在问题\(2\)的基础上扩展一下即可
inline int calc(R int n,R int q){
R int d=1;
while(d<=(q-1)*n)d=ceil(1.0*q/(q-1)*d);
return q*n+1-d;
}
参考资料
《具体数学》