﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-yuanyuelang-文章分类-转载</title><link>http://www.cppblog.com/yuanyuelang/category/13643.html</link><description /><language>zh-cn</language><lastBuildDate>Sat, 24 Apr 2010 18:26:08 GMT</lastBuildDate><pubDate>Sat, 24 Apr 2010 18:26:08 GMT</pubDate><ttl>60</ttl><item><title>约瑟夫问题总结(转载)</title><link>http://www.cppblog.com/yuanyuelang/articles/113480.html</link><dc:creator>原语饿狼</dc:creator><author>原语饿狼</author><pubDate>Sat, 24 Apr 2010 16:33:00 GMT</pubDate><guid>http://www.cppblog.com/yuanyuelang/articles/113480.html</guid><wfw:comment>http://www.cppblog.com/yuanyuelang/comments/113480.html</wfw:comment><comments>http://www.cppblog.com/yuanyuelang/articles/113480.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/yuanyuelang/comments/commentRss/113480.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/yuanyuelang/services/trackbacks/113480.html</trackback:ping><description><![CDATA[<p><a href="http://gdxzleecs.blog.sohu.com/124424792.html">http://gdxzleecs.blog.sohu.com/124424792.html</a><br><br>首先看一看最原始的约瑟夫问题：</p>
<p>1&nbsp;&nbsp; 约瑟夫环（Josephus）问题是由古罗马的史学家约瑟夫（Josephus）提出的，他参加并记录了公元66—70年犹太人反抗罗马的起义。约瑟夫作为一个将军，设法守住了裘达伯特城达47天之久，在城市沦陷之后，他和40名死硬的将士在附近的一个洞穴中避难。在那里，这些叛乱者表决说&#8220;要投降毋宁死&#8221;。于是，约瑟夫建议每个人轮流杀死他旁边的人，而这个顺序是由抽签决定的。约瑟夫有预谋地抓到了最后一签，并且，作为洞穴中的两个幸存者之一，他说服了他原先的牺牲品一起投降了罗马。 </p>
<p>2&nbsp;&nbsp;17世纪的法国数学家加斯帕在《数目的游戏问题》中讲的一个故事：15个教徒和15 个非教徒在深海上遇险，必须将一半的人投入海中，其余的人才能幸免于难，于是想了一个办法：30个人围成一圆圈，从第一个人开始依次报数，每数到第九个人就将他扔入大海，如此循环进行直到仅余15个人为止。问怎样排法，才能使每次投入大海的都是非教徒.</p>
<p>3&nbsp;&nbsp; 有ｎ只猴子，按顺时针方向围成一圈选大王（编号从１到ｎ），从第１号 <br>开始报数，一直数到ｍ，数到ｍ的猴子退出圈外，剩下的猴子再接着从1 开始报数。就这样， <br>直到圈内只剩下一只猴子时，这个猴子就是猴王，编程求输入ｎ，ｍ后，输出最后猴王的编 <br>号。 </p>
<p>4&nbsp;&nbsp;&nbsp;&nbsp; 编号为1,2,3,&#8230;,n的n个人按顺时针方向围坐一圈，每人持有一个密码（正整数）。一开始任选一个正整数作为报数的上限值m，从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止。报m的人出列，将他的密码作为新的m值，从他在顺时针方向上的下一人开始重新从1报数，如此下去，直到所有人全部出列为止。编程打印出列顺序。</p>
<p><br>以上是典型的约瑟夫问题。约瑟夫问题的传统解法是利用循环链表加以解决，这就需要从1号元素开始模拟所有元素出列的情况，附上原始的解决方法：</p>
<p><font color=#0000ff>#include&lt;stdio.h&gt;<br>#include&lt;stdlib.h&gt;<br>#include&lt;string.h&gt;<br>struct node<br>{<br>&nbsp;&nbsp;&nbsp; int code;<br>&nbsp;&nbsp;&nbsp; node * next;<br>};<br>int n,m,i,j;<br>int main(void)<br>{<br>&nbsp;&nbsp;&nbsp; node * head,* tail,* no,* p2;<br>&nbsp;&nbsp;&nbsp; no=new node;<br>&nbsp;&nbsp;&nbsp; no-&gt;code=1;<br>&nbsp;&nbsp;&nbsp; no-&gt;next=NULL;<br>&nbsp;&nbsp;&nbsp; head=tail=no;<br>&nbsp;&nbsp;&nbsp; scanf("%d%d",&amp;n,&amp;m);<br>&nbsp;&nbsp;&nbsp; for (i=2;i&lt;=n;i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; no=new node;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; no-&gt;code=i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; no-&gt;next=NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tail-&gt;next=no;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tail=no;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; tail-&gt;next=head;<br>&nbsp;&nbsp;&nbsp; for (i=1;i&lt;=n-1;i++)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j=1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while (j&lt;=m-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; no=no-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; j++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d ",no-&gt;next-&gt;code);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p2=no-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; no-&gt;next=no-&gt;next-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; delete p2;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; printf("%d\n",no-&gt;code);<br>&nbsp;&nbsp;&nbsp; system("pause");<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</font></p>
<p><font color=#0000ff>对于4 ：</font></p>
<font color=#0000ff>
<p>#include&lt;stdio.h&gt;<br>#include&lt;stdlib.h&gt;<br>/*********<br>作者： 北京交通大学 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 循环链表的应用 <br>********/<br>typedef struct LNode<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int num,code;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; struct LNode *next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>}LNode,*Linklist;<br>/*********<br>函数名：Josef<br>参数：整形n，代表人数&nbsp; m代表初始的密码 <br>返回值：无 </p>
<p>**********/<br>void Josef(int n,int m)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; Linklist head,p,L,q;<br>&nbsp;&nbsp;&nbsp;&nbsp; head = (Linklist)malloc(sizeof(LNode));<br>&nbsp;&nbsp;&nbsp;&nbsp; p = head;<br>&nbsp;&nbsp;&nbsp;&nbsp; int i,j,k,temp;<br>&nbsp;&nbsp;&nbsp;&nbsp; int mima = m;//初始密码 <br>&nbsp;&nbsp;&nbsp;&nbsp; for(i=1;i&lt;n;i++)//建立单循环链表 ,有头结点 <br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; L = (Linklist)malloc(sizeof(LNode));<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p-&gt;next = L;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = L;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; L-&gt;next = head;//L指向队尾; <br>&nbsp;&nbsp;&nbsp;&nbsp; //L = head;<br>&nbsp;&nbsp;&nbsp;&nbsp; //p = head-&gt;n;<br>&nbsp;&nbsp;&nbsp;&nbsp; p = head;<br>&nbsp;&nbsp;&nbsp;&nbsp; for(i = 1;i&lt;=n;i++)//为每一个节点输入密码和号数 <br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("请为节点%d输入密码：",i);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scanf("%d",&amp;j);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p-&gt;num = i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p-&gt;code= j;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = p-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; int count = 0;&nbsp;&nbsp;&nbsp; //count来寻找删除的节点的前一个节点 <br>&nbsp;&nbsp;&nbsp; p = L;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //从尾指针 开始查找，来防止初始密码为1时可能带来的指针溢出问题 <br>&nbsp;&nbsp;&nbsp; while(p-&gt;next!=p)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while(count&lt;mima-1)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p = p-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count++;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; mima = p-&gt;next-&gt;code;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("%d ",p-&gt;next-&gt;num);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p-&gt;next = p-&gt;next-&gt;next;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; printf("%d",p-&gt;num);<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>}<br>main()<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int n,m;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("请输入人数：");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scanf("%d",&amp;n);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printf("请输入初始密码：");<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; scanf("%d",&amp;m);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Josef(n,m);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; getchar();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>}</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</font>
<p>以上时间复杂度高达O(nm)，当n，m非常大(例如上百万，上千万)的时候，几乎是没有办法在短时间内出结果的。</p>
<p><font color=#ff0000>有的问题例如猴子选大王</font><font style="BACKGROUND-COLOR: #ff0000"><font color=#ff0000><font style="BACKGROUND-COLOR: #ffffff">仅仅是要求出最后的胜利者的序号，而不是要读者模拟整个过程。因此如果要追求效率，就要打破常规，实施一点数学策略。</font><br></font></font>为了讨论方便，先把问题稍微改变一下，并不影响原意： </p>
<p>问题描述：n个人（编号0~(n-1))，从0开始报数，报到(m-1)的退出，剩下的人继续从0开始报数。求胜利者的编号。</p>
<p>我们知道第一个人(编号一定是m%n-1) 出列之后，剩下的n-1个人组成了一个新的约瑟夫环（以编号为k=m%n的人开始）:<br>&nbsp; k&nbsp; k+1&nbsp; k+2&nbsp; ... n-2, n-1, 0, 1, 2, ... k-2<br>并且从k开始报0。</p>
<p>现在我们把他们的编号做一下转换：<br>k&nbsp;&nbsp;&nbsp;&nbsp; --&gt; 0<br>k+1&nbsp;&nbsp; --&gt; 1<br>k+2&nbsp;&nbsp; --&gt; 2<br>...<br>...<br>k-2&nbsp;&nbsp; --&gt; n-2<br>k-1&nbsp;&nbsp; --&gt; n-1</p>
<p>变换后就完完全全成为了(n-1)个人报数的子问题，假如我们知道这个子问题的解：例如x是最终的胜利者，那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗？！！变回去的公式很简单，相信大家都可以推出来：x'=(x+k)%n</p>
<p>如何知道(n-1)个人报数的问题的解？对，只要知道(n-2)个人的解就行了。(n-2)个人的解呢？当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题！好了，思路出来了，下面写递推公式：</p>
<p>令f[i]表示i个人玩游戏报m退出最后胜利者的编号，最后的结果自然是f[n]</p>
<p>递推公式<br>f[1]=0;<br>f[i]=(f[i-1]+m)%i;&nbsp; (i&gt;1)</p>
<p>有了这个公式，我们要做的就是从1-n顺序算出f[i]的数值，最后结果是f[n]。因为实际生活中编号总是从1开始，我们输出f[n]+1</p>
<p>由于是逐级递推，不需要保存每个f[i]，程序也是异常简单：</p>
<p>#include &lt;stdio.h&gt;<br>int main()<br>{<br>&nbsp; int n, m, i, s=0;<br>&nbsp; printf ("N M = "); scanf("%d%d", &amp;n, &amp;m);<br>&nbsp; for (i=2; i&lt;=n; i++) s=(s+m)%i;<br>&nbsp; printf ("The winner is %d\n", s+1);<br>}</p>
<p>这个算法的时间复杂度为O(n)，相对于模拟算法已经有了很大的提高。算n，m等于一百万，一千万的情况不是问题了。可见，适当地运用数学策略，不仅可以让编程变得简单，而且往往会成倍地提高算法执行效率。</p>
<p><font color=#ff0000>在此基础上再看看POJ 2244这个问题：</font></p>
<p>问题描述：<a href="http://acm.pku.edu.cn/JudgeOnline/problem?id=2244"><u><font color=#800080>http://acm.pku.edu.cn/JudgeOnline/problem?id=2244</font></u></a></p>
<p>这个问题对约瑟夫问题有点变形： 已知最后的元素 为 2&nbsp; ，n 从输入中给出，求出最小的m</p>
<p>其实这个用一种比较笨的办法就是 m 从1开始一个一个试，找到第一个（n ，m），使得最后出列的元素序号为2 的 m 即为解。</p>
<p>求最后出列元素的方法由上面给出，这里所作的优化就是求约瑟夫的优化，至于遍历求m的优化可以考虑打表。</p>
<p>这个题目还有一点要注意的就是 ： 它第一个打印的元素始终是1，从2开始为约瑟夫问题，这样可以把2看做是1，n看为n-1的约瑟夫问题。</p>
<p>一下是源代码：</p>
<p><font color=#0000ff>#include&lt;stdio.h&gt;<br>#include&lt;stdlib.h&gt;<br>int josef(int n,int m)<br>{<br>&nbsp;int s = 0;<br>&nbsp;for(int i = 2;i&lt;=n-1;i++)<br>&nbsp;&nbsp;s = (s+m)%i;<br>&nbsp;return s+1+1;<br>}<br>int main()<br>{<br>&nbsp;&nbsp;&nbsp; int n;<br>&nbsp;&nbsp;&nbsp; int m;<br>&nbsp;&nbsp;&nbsp; scanf("%d",&amp;n);<br>&nbsp;&nbsp;&nbsp; while(n)<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;for(m=1;;)<br>&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;if(josef(n,m)==2)<br>&nbsp;&nbsp;&nbsp;&nbsp;break;<br>&nbsp;&nbsp;&nbsp;m++;<br>&nbsp;&nbsp;}<br>&nbsp;&nbsp;printf("%d\n",m);<br>&nbsp;&nbsp;scanf("%d",&amp;n);<br>&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; return 0;<br>}</font><br></p>
<img src ="http://www.cppblog.com/yuanyuelang/aggbug/113480.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/yuanyuelang/" target="_blank">原语饿狼</a> 2010-04-25 00:33 <a href="http://www.cppblog.com/yuanyuelang/articles/113480.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>