码迷,mamicode.com
首页 > 其他好文 > 详细

约瑟夫问题

时间:2019-11-30 09:43:13      阅读:61      评论:0      收藏:0      [点我收藏+]

标签:inline   ret   end   号码   floor   n+2   数学   就是   做了   

全都是抄具体数学的

问题\(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;
}

参考资料

《具体数学》

约瑟夫问题

标签:inline   ret   end   号码   floor   n+2   数学   就是   做了   

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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!