约瑟夫问题

全都是抄具体数学的

问题\(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\),那么递归过程就变成了

\[\begin{aligned} d &=3n+1-\left(\lfloor{{3n+1-d-n-1}\over 2}\rfloor+3n+1-d-n\right)\\ &=n+d-\lfloor{2n-d\over 2}\rfloor\\ &=d-\lfloor{-d\over 2}\rfloor\\ &=d+\lceil{d\over 2}\rceil\\ &=\lceil{3d\over 2}\rceil\\ \end{aligned} \]

代码如下

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;
}

参考资料

《具体数学》

posted @ 2019-11-30 08:20  源曲明  阅读(204)  评论(0编辑  收藏  举报