猴子选大王问题.docx
《猴子选大王问题.docx》由会员分享,可在线阅读,更多相关《猴子选大王问题.docx(3页珍藏版)》请在冰豆网上搜索。
猴子选大王问题
Preparedon22November2020
猴子选大王问题
这是17世纪的法国数学家加斯帕在《数目的游戏问题》中讲的一个故事:
15个教徒和15个非教徒在深海上遇险,必须将一半的人投入海中,其余的人才能幸免于难,于是想了一个办法:
30个人围成一圆圈,从第一个人开始依次报数,每数到第九个人就将他扔入大海,如此循环进行直到仅余15个人为止。
问怎样排法,才能使每次投入大海的都是非教徒。
*问题分析与算法设计 约瑟夫问题并不难,但求解的方法很多;题目的变化形式也很多。
这里给出一种实现方法。
题目中30个人围成一圈,因而启发我们用一个循环的链来表示。
可以使用结构数组来构成一个循环链。
结构中有两个成员,其一为指向下一个人的指针,以构成环形的链;其二为该人是否被扔下海的标记,为1表示还在船上。
从第一个人开始对还未扔下海的人进行计数,每数到9时,将结构中的标记改为0,表示该人已被扔下海了。
这样循环计数直到有15个人被扔下海为止。
[编辑本段]约瑟夫问题的一般形式:
约瑟夫问题是个有名的问题:
N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。
例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。
最后剩下1号。
假定在圈子里前K个为好人,后K个为坏人,你的任务是确定这样的最少M,使得所有的坏人在第一个好人之前被杀掉。
C++代码示例:
#include usingnamespacestd; voidmain() { intn,m,a[101],k,i,j,num;.n-2,n-1,0,1,2,...k-2 并且从k开始报0。
现在我们把他们的编号做一下转换:
k-->0 k+1-->1 k+2-->2 ... ... k-2-->n-2 k-1-->n-1 变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:
例如x是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗!
!
变回去的公式很简单,相信大家都可以推出来:
x'=(x+k)modn 如何知道(n-1)个人报数的问题的解对,只要知道(n-2)个人的解就行了。
(n-2)个人的解呢当然是先求(n-3)的情况----这显然就是一个倒推问题!
好了,思路出来了,下面写递推公式:
令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n] 递推公式 f[1]=0; f=(f+m)modi;(i>1) 有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是f[n]。
因为实际生活中编号总是从1开始,我们输出f[n]+1 由于是逐级递推,不需要保存每个f,程序也是异常简单:
c++ #include<> intmain() { intn,m,i,s=0; printf("NM=");scanf("%d%d",&n,&m); for(i=2;i<=n;i++)s=(s+m)%i; printf("Thewinneris%d\n",s+1); } pascal varn,m,i,s:
integer; begin write('NM='); read(n,m); fori:
=2tondo s:
=(s+m)modi; writeln('Thewinneris',s+1); end. 这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。
算n,m等于一百万,一千万的情况不是问题了。
可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。
约瑟夫问题10e100版(fromvijios) 描述Description n个人排成一圈。
从某个人开始,按顺时针方向依次编号。
从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。
这样不断循环下去,圈子里的人将不断减少。
由于人的个数是有限的,因此最终会剩下一个人。
试问最后剩下的人最开始的编号。
输入格式InputFormat 一个正整数n,表示人的个数。
输入数据保证数字n不超过100位。
输出格式OutputFormat 一个正整数。
它表示经过“一二一”报数后最后剩下的人的编号。
样例输入SampleInput 9 样例输出SampleOutput 3 时间限制TimeLimitation 各个测试点1s 注释Hint 样例说明 当n=9时,退出圈子的人的编号依次为:
24681597 最后剩下的人编号为3 初见这道题,可能会想到模拟。
可是数据实在太大啦!
!
我们先拿手来算,可知n分别为1,2,3,4,5,6,7,8...时的结果是1,1,3,1,3,5,7,1... 有如下规律:
从1到下一个1为一组,每一组中都是从1开始递增的奇数,且每组元素的个数分别为1,2,4... 这样就好弄了!
!
大体思路如下:
①read(a) ②b:
=1,c:
=1{b为某一组的元素个数,c为现在所加到的数} ③whilec=b*2,c:
=b+c){超过目标时停止加数} ⑥c:
=c-b{退到前一组} ⑦x:
=a-c{算出目标为所在组的第几个元素} ⑧ans:
=x*2-1{求出该元素} ⑨write(ans) 有了思路,再加上高精度就可以了。
我写的代码比较猥琐,因为是先把上面的思路敲进去,再写过程,又把一些简单的过程合到主程序中了,所以有点乱,也有点猥琐。
起提供思路的作用还是完全可以的吧~~~ vara,b,c:
array[1..105]ofinteger; la,lb,lc,i:
integer; s:
string; procedureincc; vari:
integer; begin fori:
=1to105doc:
=c+b; fori:
=1to104doifc>9then begin c:
=c+cdiv10; c:
=cmod10; end; end; functioncxiaoa:
boolean; vari:
integer; begin cxiaoa:
=false; fori:
=105downto1do ifc=true;break;end elseifc>athenbreak; end; proceduredoubleb; vari:
integer; begin fori:
=1to105dob:
=b*2; fori:
=1to104doifb>9then begin b:
=b+bdiv10; b:
=bmod10; end; end; proceduredecc; vari,j:
integer; begin fori:
=1to104do ifc>=bthenc:
=c-belse begin j:
=i+1; whilec[j]=0doinc(j); whilej>ido begin c[j]:
=c[j]-1; c[j-1]:
=c[j-1]+10; dec(j); end; c:
=c-b; end; end; procedurefua; vari:
integer; begin fori:
=1to104do ifa>cthena:
=a-celse begin a:
=a-1; a:
=a+10; a:
=a-c; end; end; procedureoutit; vari,j:
integer; begin fori:
=1to105doa:
=a*2; fori:
=1to104doifa>9then begin a:
=a+adiv10; a:
=amod10; end; ifa[1]>0thena[1]:
=a[1]-1else begin j:
=2; whilea[j]=0doinc(j); whilej>1do begin a[j]:
=a[j]-1; a[j-1]:
=a[j-1]+10; dec(j); end; a[1]:
=a[1]-1; end; fori:
=105downto1doifa>0thenbeginj:
=i;break;end; fori:
=jdownto1dowrite(a); end; begin readln(s); la:
=length(s); fori:
=ladownto1doa:
=ord(s[la+1-i])-ord('0'); b[1]:
=1; c[1]:
=1; whilecxiaoado begin doubleb; incc; end; decc; fua; outit; end.[编辑本段]约瑟夫问题的另外一个有名的例子:
[编辑本段]猴子选大王 一.问题描述:
一堆猴子都有编号,编号是1,2,3...m,这群猴子(m个)按照1-m的顺序围坐一圈,从第1开始数,每数到第N个,该猴子就要离开此圈,这样依次下来,直到圈中只剩下最后一只猴子,则该猴子为大王。
二.基本要求:
(1)输入数据:
输入m,nm,n为整数,n(2)中文提示按照m个猴子,数n个数的方法,输出为大王的猴子是几号,建立一个函数来实现此功能 C程序:
#include<> #include<> #defineLENsizeof(structmonkey)10000]ofinteger; n,s,i,j:
integer; begin read(m,n); fori:
=1tomdoa[i]:
=1; j:
=0; fori:
=1tomdo begin s:
=0; whiles=1; s:
=s+a[j]; end; write(j); a[j]:
=0; end; end. #include<> intmain() { intn,m,i,s=0; printf("NM=");scanf("%d%d",&n,&m); for(i=2;i<=n;i++)s=(s+m)%i; printf("Thewinneris%d\n",s+1); return0; } 约瑟夫数学算法 #include<> #include<> intmain(void) { intn,i=0,m,p; scanf("%d%d",&n,&m);.,a(n-1),an 从a1开始报数,一圈之后,剩下的人为a1,a2,a4,a5,...a(n-nmod3-1),a(n-nmod3+1),..,an 于是可得:
1、这一轮中最后一个死的是a(n-nmod3),下一轮第一个报数的是a(n-nmod3+1) 2、若3|n,则最后死的人为新一轮的第F(n-[n/3])个人 若nmod3≠0且f(n-[n/3])<=nmod3则最后死的人为新一轮的第n-[n/3]+F(n-[n/3])-(nmod3)人 若nmod3≠0且f(n-[n/3])>nmod3则最后死的人为新一轮的第F(n-[n/3])-(nmod3)人 3、新一轮第k个人对应原来的第3*[(k-1)/2]+(k-1)mod2+1个人 综合1,2,3可得:
F
(1)=1,F
(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1, 当f(n-[n/3])<=nmod3时k=n-[n/3]+F(n-[n/3])-(nmod3),F(n)=3*[(k-1)/2]+(k-1)mod2+1 当f(n-[n/3])>nmod3时k=F(n-[n/3])-(nmod3),F(n)=3*[(k-1)/2]+(k-1)mod2+1 这种算法需要计算[log(3/2)2009]次这个数不大于22,可以用笔算了 于是:
第一圈,将杀死669个人,这一圈最后一个被杀死的人是2007,还剩下1340个人, 第二圈,杀死446人,还剩下894人 第三圈,杀死298人,还剩下596人 第四圈,杀死198人,还剩下398人 第五圈,杀死132人,还剩下266人 第六圈,杀死88人,还剩下178人 第七圈,杀死59人,还剩下119人 第八圈,杀死39人,还剩下80人 第九圈,杀死26人,还剩下54人 第十圈,杀死18人,还剩36人 十一圈,杀死12人,还剩24人 十二圈,杀死8人,还剩16人 十三圈,杀死5人,还剩11人 十四圈,杀死3人,还剩8人 十五圈,杀死2人,还剩6人 F
(1)=1,F
(2)=2,F(3)=2,F(4)=1,F(5)=4,F(6)=1, 然后逆推回去 F(8)=7F(11)=7F(16)=8f(24)=11f(36)=16f(54)=23f(80)=31f(119)=43f(178)=62f(266)=89f(398)=130 F(596)=191F(894)=286F(1340)=425F(2009)=634