猴子选大王问题

  • 格式:docx
  • 大小:563.26 KB
  • 文档页数:15

下载文档原格式

  / 15
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

猴子选大王问题 Prepared on 22 November 2020

这是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

using namespace std;

void main()

{

int n,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) mod n

如何知道(n-1)个人报数的问题的解对,只要知道(n-2)个人的解就行了。(n-2)个人的解呢当然是先求(n-3)的情况 ---- 这显然就是一个倒推问题!好了,思路出来了,下面写递推公式:

令f表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n] 递推公式

f[1]=0;

f=(f+m) mod i; (i>1)

有了这个公式,我们要做的就是从1-n顺序算出f的数值,最后结果是

f[n]。因为实际生活中编号总是从1开始,我们输出f[n]+1

由于是逐级递推,不需要保存每个f,程序也是异常简单:

c++

#include <>

int main()

{

int n, m, i, s=0;

printf ("N M = "); scanf("%d%d", &n, &m);

for (i=2; i<=n; i++) s=(s+m)%i;

printf ("The winner is %d\n", s+1);

}

pascal

var n,m,i,s:integer;

begin

write('N M =');

read(n,m);

for i:=2 to n do

s:=(s+m) mod i;

writeln('The winner is ',s+1);

end.

这个算法的时间复杂度为O(n),相对于模拟算法已经有了很大的提高。算

n,m等于一百万,一千万的情况不是问题了。可见,适当地运用数学策略,不仅可以让编程变得简单,而且往往会成倍地提高算法执行效率。

约瑟夫问题10e100版(from vijios)

描述 Description

n个人排成一圈。从某个人开始,按顺时针方向依次编号。从编号为1的人开始顺时针“一二一”报数,报到2的人退出圈子。这样不断循环下去,圈子里的人将不断减少。由于人的个数是有限的,因此最终会剩下一个人。试问最后剩下的人最开始的编号。

输入格式 Input Format

一个正整数n,表示人的个数。输入数据保证数字n不超过100位。

输出格式 Output Format

一个正整数。它表示经过“一二一”报数后最后剩下的人的编号。

样例输入 Sample Input

9

样例输出 Sample Output

3

时间限制 Time Limitation

各个测试点1s

注释 Hint

样例说明

当n=9时,退出圈子的人的编号依次为:

2 4 6 8 1 5 9 7

最后剩下的人编号为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为现在所加到的数}

③while c

⑥c:=c-b{退到前一组}

⑦x:=a-c{算出目标为所在组的第几个元素}

⑧ans:=x*2-1{求出该元素}

⑨write(ans)

有了思路,再加上高精度就可以了。我写的代码比较猥琐,因为是先把上面的思路敲进去,再写过程,又把一些简单的过程合到主程序中了,所以有点乱,也有点猥琐。起提供思路的作用还是完全可以的吧~~~

var a,b,c:array[1..105]of integer;

la,lb,lc,i:integer;

s:string;

procedure incc;