猴子选大王问题.docx

上传人:b****4 文档编号:4072300 上传时间:2022-11-27 格式:DOCX 页数:3 大小:18.91KB
下载 相关 举报
猴子选大王问题.docx_第1页
第1页 / 共3页
猴子选大王问题.docx_第2页
第2页 / 共3页
猴子选大王问题.docx_第3页
第3页 / 共3页
亲,该文档总共3页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

猴子选大王问题.docx

《猴子选大王问题.docx》由会员分享,可在线阅读,更多相关《猴子选大王问题.docx(3页珍藏版)》请在冰豆网上搜索。

猴子选大王问题.docx

猴子选大王问题

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

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 工程科技 > 电子电路

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1