约瑟夫问题

全都是抄具体数学的

问题(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-1over 2} floor),而它之前的号码是(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),那么递归过程就变成了

[egin{aligned} d &=3n+1-left(lfloor{{3n+1-d-n-1}over 2} floor+3n+1-d-n ight)\ &=n+d-lfloor{2n-dover 2} floor\ &=d-lfloor{-dover 2} floor\ &=d+lceil{dover 2} ceil\ &=lceil{3dover 2} ceil\ 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;
}

参考资料

《具体数学》

原文地址:https://www.cnblogs.com/yuanquming/p/11961246.html