约瑟夫斯问题求解

合集下载

抽杀问题-约瑟夫问题

抽杀问题-约瑟夫问题

[阅读材料]世界名题与小升初之:抽杀问题(約瑟夫问题)--马到成功老师在各类竞赛中,各类小升初考试中相关的世界名题出现的概率极高,这是由小升初与数学竞赛的特点决定,这特点便是:知识性,趣味性,思想性相结合。

先给大家介绍这一问题的由来。

据说著名犹太历史学家Josephus有过以下的故事:在罗马人占领乔塔帕特後,39 個犹太人与Josephus及他的朋友躲到一個洞中,39個犹太人決定宁愿死也不要被人抓到,于是決定了一个自杀方式,41個人排成一个圆圈,由第1個人开始报数,每报数到第3人该人就必須自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止。

然而Josephus 和他的朋友并不想遵从,Josephus要他的朋友先假装遵从,他將朋友与自己安排在第16個与第31個位置,于是逃过了这场死亡游戏。

解法約瑟夫问题可用代数分析來求解,将这个问题扩大好了,假设现在您与m个朋友不幸参与了这个游戏,您要如何保护您的朋友?只要画两个圆圈就可以让自己与朋友免于死亡游戏,这两个圆圈是排列顺序,而外圈是自杀顺序,如下图所示:使用程式来求解的话,只要将阵列当作环状来处理就可以了,在列中由计数1开始,每找到三个无资料区就填入一个计数,直接计数來求解的話,只要將阵列当作环状来处理就可以了,在阵列中由計数1开始,每找到三个无资料区就填入一个計数,直而計数达41为止,然后將阵列由索引1开始列出,就可以得知每个位置的自杀順序,这就是約瑟夫排列,41個人报数3的約瑟夫排列如下所示:14 36 1 38 15 2 24 30 3 16 34 4 25 17 5 40 31 6 18 26 7 37 19 8 35 27 9 20 32 10 41 21 11 28 39 12 22 33 13 29 23由上可知,最后一個自杀的是在第31个位置,而倒数第二个自杀的要排在第16个位置,之前的人都死光了,所以他们也就不知道約瑟夫与他的朋友并没有遵守游戏规则了。

约瑟夫问题

约瑟夫问题

一问题描述1 题目内容:约瑟夫(Joseph)问题的一种描述是:编号为1,2,..., n的n 个人按顺时针方向围坐一圈, 每人持有一个密码(正整数)。

一开始选任一个正整数作为报数上限值m,从第一个人开始按顺时针方向自1开始顺序报数,报到m时停止报数。

报m的人出列,将它的密码作为新的m值。

试设计一个程序求出出列顺序。

2 基本要求:利用单项循环链表存储结构模拟此过程,按照出列的顺序印出各人的编号。

3 测试数据:m的初值为20;n=7,7个人的密码依次为:3,1,7,2,4,8,4(正确的出列顺序应为6,1,4,7,2,3,5)。

二需求分析程序运行后,首先要求用户指定初始报数上限值,然后读取个人的密码。

输入数据:建立输入处理输入数据,输入m的初值,n ,输入每个人的密码,建立单循环链表。

输出形式:建立一个输出函数,将正确的输出序列三概要设计利用单项循环链表存储结构模拟此过程1 循环链表的抽象数据类型循环链表是单链表的一种变化形式,把单链表的最后一个节点的next指针指向第一个节点,整个链表就形成了一个环。

2 循环链表的基本操作(仅列出用在本程序的)creat(n)操作结果:构造一个长度为n的无头节点的循环链表,并返回指向最后一个节点的指针find(m,s)初始条件:循环链表存在操作结果:找到当前元素(即s)后面第m个元素print(&m,&n,&s)初始条件:循环链表存在操作结果:从s中删除约舍夫问题中下一个被删除的元素,并将此元素显示在屏幕上3 本程序包括4个模块:主程序模块;创建循环链表模块;找节点模块;删节点模块;各模块调用关系如下图所示:4 约舍夫问题的伪码算法void main( ){输入参与的人数;输入第一个密码;创建无头节点的循环链表;输出第一个出列元素;输出剩余出列元素;}四详细设计1 实现概要设计的数据类型typedef struct LNode{int data;int num;struct LNode *next;}LNode,*linklist; //无头节点的循环链表的节点类型2 每个子函数的算法linklist creat(int n){/*构造一个长度为n的无头节点的循环链表,并返回指向最后一个节点的指针*/linklist head,s; //head为头节点标记s为链表中节点int i;s=head=(linklist)malloc(sizeof(LNode)); //创建头节点for(i=1;i<n;i++) //建立循环链表{s->data=i;printf("num%d: ",i);scanf("%d",&(s->num));/*输入第i个人的密码*/while(s->num<=0){/*如果输入的s->num小于等于0,要求重新输入*/ printf("请重新输入\nnum%d: ",i);scanf("%d",&s->num);}s->next=(linklist)malloc(sizeof(LNode)); //开辟下一个节点s=s->next;}s->data=i;printf("num%d: ",i);scanf("%d",&(s->num));s->next=head;return(s);}linklist find(int m,linklist s) //找到当前元素后面第m个元素{int i;for(i=0;i<m-1;i++)s=s->next;return(s); //返回找到元素的指针}void print(into &mint &n,linklist &s){linklist p;s=find(m,s); //找到待删除的元素printf("%d ",s->next->data);/*输出找到的元素*/m=s->next->num;/*将此元素从链表中删除,并释放此节点*/ p=s->next;s->next=s->next->next;free(p);--n; //约舍夫环中节点数少一}3 主程序算法void main( ){/*解决约舍夫问题的主函数*/int n,m; //n为约舍夫环内初始人数m为初始密码printf("type in n :");scanf("%d",&n);/*输入n*/while(n<=0){/*如果输入的n小于等于0,要求重新输入*/printf("please type n in again \ntype in n :");scanf("%d",&n);}printf("type in m :");scanf("%d",&m);/*输入m*/while(m<0){/*如果输入的m小于0,要求重新输入*/printf("please type m in again \ntype in m :");scanf("%d",&m);}linklist s;s=creat(n);/*创建无头节点的循环链表,返回指向最后一个元素的指针*/printf("the sequence is ");print(m,n,s);//输出第一个出列的元素while(n){print(m,n,s);//输出剩余出列的元素}printf("\n");}4 函数调用关系图五调试分析调试过程中出现过如下问题:1 开始编程序时没考虑输入错误的问题,导致输入错误后程序出错2 编程序时删除节点子程序结束条件出错3 对开辟的节点用完后没有释放六使用说明程序运行后按提示输入n和m的值,在输入约舍夫环中每个人的密码,运行即可得到出列顺序七测试结果进入程序后要求输入n的值然后输入m的值再输入每个人的密码最后得到出列顺序八附录(源程序)这里附上两种源程序,本质上相同,只是第一个程序按老师要求写为很多子函数形式,第二个是我已开始编的,一个大函数。

约瑟夫问题多种解决方法

约瑟夫问题多种解决方法

• • • • • • • • • • •
s:=0; while s<n do begin if j<m then inc(j) else j:=1; s:=s+a[j]; end; write(j); a[j]:=0; end; end.

约瑟夫问题多 种解决方法
约瑟夫问题的来历
• 据说著名犹太历史学家 Josephus有过以下的故事:在罗 马人占领乔塔帕特后,39 个犹太人与Josephus及他的朋 友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓 到,于是决定了一个自杀方式,41个人排成一个圆圈,由 第1个人开始报数,每报数到第3人该人就必须自杀,然后 再由下一个重新报数,直到所有人都自杀身亡为止。然而 Josephus 和他的朋友并不想遵从,Josephus要他的朋友 先假装遵从,他将朋友与自己安排在第16个与第31个位置, 于是逃过了这场死亡游戏。17世纪的法国数学家加斯帕在 《数目的游戏问题》中讲了这样一个故事:15个教徒和15 个非教徒在深海上遇险,必须将一半的人投入海中,其余 的人才能幸免于难,于是想了一个办法:30个人围成一圆 圈,从第一个人开始依次报数,每数到第九个人就将他扔 入大海,如此循环进行直到仅余15个人为止。问怎样排法, 才能使每次投入大海的都是非教徒。
著名约瑟夫问题一
• 17世纪的法国数学家加斯帕在《数目的游戏问题》中讲了 这样一个故事:15个教徒和15 个非教徒在深海上遇险, 必须将一半的人投入海中,其余的人才能幸免于难,于是 想了一个办法:30个人围成一圆圈,从第一个人开始依次 报数,每数到第九个人就将他扔入大海,如此循环进行直 到仅余15个人为止。问怎样排法,才能使每次投入大海的 都是非教徒。题目中30个人围成一圈,因而启发我们用一 个循环的链来表示。可以使用结构数组来构成一个循环链。 结构中有两个成员,其一为指向下一个人的指针,以构成 环形的链;其二为该人是否被扔下海的标记,为1表示还 在船上。从第一个人开始对还未扔下海的人进行计数,每 数到9时,将结构中的标记改为0,表示该人已被扔下海了。 这样循环计数直到有15个人被扔下海为止

约瑟夫斯问题探究

约瑟夫斯问题探究

约瑟夫斯问题探究学而思培优陈绍伦问题背景据说著名犹太历史学家约瑟夫斯有过以下的故事:在罗马人占领乔塔帕特后,39个犹太人与约瑟夫斯及他的朋友躲到一个洞中,他们宁死也不愿被敌人抓到,于是决定了一个自杀方式:41个人排成一个圆圈,由第1个人开始报数,每报数到第3人该人就必须自杀,然后再由下一个重新报数,直到所有人都自杀身亡为止.可是约瑟夫斯和他的朋友并不想遵从,他们打算选择合适的位置,留到最后避免处决.于是,他将朋友与自己安排在第16个与第31个位置,逃过了这场死亡游戏.也因此,我们以故事的主人公命名这一系列的“抽杀”问题.情景一:直线型让我们先从比较简单的直线型猫吃老鼠着手研究.【问题1】(隔1吃1)猫将4只老鼠排成一行,先隔1只,再吃1只,再隔1只,再吃1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.其实这题更像是一个“脑筋急转弯”:第一只老鼠有可能被吃掉吗?答案显而易见!结论:排成一行,隔1吃1,留下的总是第1只.【问题2】(吃1隔1)(1)猫将4只老鼠排成一行,先吃1只,再隔1只,再吃1只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将10只老鼠排成一行,先吃1只,再隔1只,再吃1只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.对于数字比较简单的情况,实际动手操作是得到答案的好方法!(1)4只老鼠时:第一轮猫吃掉1、跳过2、吃掉3、跳过4,留下2、4;第二轮猫吃掉2,因此最后剩下的老鼠是原来的第4只.(2)10只老鼠时:第一轮猫吃掉1、跳过2、吃掉3、跳过4、……,留下2、4、6、8、10;第二轮猫吃掉2,跳过4、吃掉6、跳过8、吃掉10,留下4、8;第三轮猫吃掉4,因此最后剩下的老鼠是原来的第8只.观察每轮留下的老鼠位置,我们发现一个有趣的规律:处于第奇数个位置的老鼠,总会被吃掉;而处于第偶数个位置的老鼠留到下一轮.而且,能留下来的老鼠,在下一轮中位置编号也发生了变化:若前一轮在第2k(k为非0自然数)个位置,下一轮将在第k个位置.换句话说,老鼠编号能分解出几个质因数2,这只老鼠就能挺过前几轮!那么结论就显而易见了:结论:排成一行,吃1隔1,留下的是第2n只.(取范围内最大值)【问题3】(吃2隔1)(1)猫将10只老鼠排成一行,先吃2只,再隔1只,再吃2只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将20只老鼠排成一行,先吃2只,再隔1只,再吃2只,再隔1只,……一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.【问题2】中观察的是因数2的个数,因为“吃1隔1”是以每2只老鼠为一个周期.那么,本题的“吃2隔1”,会不会与因数3的个数有关呢?我们来实际操作一下:(1)10只老鼠时:第一轮猫吃掉1和2、跳过3、吃掉4和5、跳过6,……,留下3、6、9;第二轮猫吃掉3和6,因此最后剩下的老鼠是原来的第9只.还真是!9可以分解出2个因数3,是范围内最多的,它留到了最后.(2)20只老鼠时:第一轮猫吃掉1和2、跳过3、吃掉4和5、跳过6,......,留下3、6、 (18)第二轮猫吃掉3和6、跳过9、吃掉12和15、跳过18,留下9、18;第三轮猫吃掉9、因此最后剩下的老鼠是原来的第18只.这里就有个小问题了:9和18都能分解出2个因数3,为什么留到最后的18呢?重新观察第二轮的结果:9和18都有2个因数3,所以它们都挺过了第2轮.同时,由于都没有第3个因数3,如果猫要继续吃下去的话,它们都无法挺过第3轮!这么看来,18能留到最后的原因,仅仅是因为它的编号比9大而已!于是我们可以得出结论了:结论:排成一行,吃2隔1,留下的是第3n或第23n×只.(取范围内最大值)对于一般情况“排成一行,吃k隔1”,用同样的方法可以得到类似的结论,最终留下的编号是“能分解出因数(1)k+的个数最多且范围内最大的数”.【问题4】(吃1隔1吃1)(1)猫将9只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将27只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.“吃-隔-吃”也是每3只老鼠为一个周期的.因此我们先从总数为3n时探讨规律:(1)9只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8;第二轮猫吃掉2和8,因此最后剩下的老鼠是原来的第5只.(2)27只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,......,留下2、5、 (26)第二轮猫吃掉2和8、跳过5、吃掉11和17、跳过14、……,留下5、14、23;第三轮猫吃掉5和23,因此最后剩下的老鼠是原来的第14只.答案的5和14,与总数9和27存在什么样的联系呢?这一规律,需要些许灵感才能观察出来:5是1~9的中间数,14是1~27的中间数.当然,这并不只是随意猜测,“中间数”的规律是可以解释的:总数为3的时候,显然留下的是正中间的第2只;总数为9的时候,我们将老鼠们分为3组:{1,2,3}、{4,5,6}、{7,8,9},留到最后的老鼠,它就应该是正中间一组的正中间一个,当然,它也处于所有数的正中间.如此类推,总数为27、81的时候,留下的也是正中间的第14只、第41只老鼠.根据等差数列的知识,我们可以用下列公式将中间数表示出来:结论:3n只,排成一行,“吃-隔-吃”,留下的是第312n+只.也就是说,答案一定在2、5、14、41、……这些数之中.我们得到了一个漂亮的结论!但是,上述结论的前提是“总数为3n只”.如果总数不是3的幂呢?【问题5】(吃1隔1吃1)(1)猫将12只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将16只老鼠排成一行,按“吃-隔-吃”的规律吃掉老鼠,一轮结束后再次重新开始,直到最后剩下1只老鼠,这只老鼠是原来的第________只.(1)12只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8、11;第二轮猫吃掉2和8、跳过5、吃掉11,因此最后剩下的老鼠是原来的第5只.(2)16只老鼠时:第一轮猫吃掉1和3、跳过2、吃掉4和6、跳过5,……,留下2、5、8、11、14;第二轮猫吃掉2和8、跳过5、吃掉11、跳过14、……,留下5、14;第三轮猫吃掉5,因此最后剩下的老鼠是原来的第14只.这些结果,在【问题4】中曾经也出现过,也就是说答案仍是312n+.为什么呢?在这里,我们可以用倒推的方法进行思考:(1)显然,当老鼠数量不超过3,最终将留下2;(2)若这一轮老鼠数量不超过3,那么上一轮的老鼠总数不超过9.此时,这一轮的2在上一轮的编号是5;(3)若这一轮老鼠数量不超过9,那么上一轮的老鼠总数不超过27.此时,这一轮的5在上一轮的编号是14;(4)若这一轮老鼠数量不超过27,那么上一轮的老鼠总数不超过81.此时,这一轮的5在上一轮的编号是41;……如此递推可知,活到最终轮的老鼠在每一轮的编号,倒过来数依次是2、5、14、41、……换句话说,答案应该是312n+,而且应取范围内数值最大的一个!结论:排成一行,“吃-隔-吃”,留下的是第312n+只.(取范围内最大值)情景二:封闭型如果把“猫吃老鼠”问题放在圆环上研究,又会有怎么样的变化?【问题6】(吃1隔1)(1)猫将4只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将8只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.简单枚举后发现,总数为4时留下的就是第4只;总数为8时留下的就是第8只. 这个结果与直线型的情况完全一致.巧合的是最后一只老鼠总被跳过,所以新的一轮又可以看成是“先吃后隔”的问题,直线型中的“从头开始”,最终结果当然一致.我们还可以用递推的思路理解:“总数为8的隔1吃1问题”,操作一轮后恰好变为“总数为4的隔1吃1问题”.也就是说,最终留下的将是第二轮的第4只,而它也是第一轮的第8只.所以,如果总数为4时留下的是最后一只,那么总数为8时留下的也是最后一只. 同理,总数为16可变为总数为8,总数为32可变为总数为16……因此得出结论:结论:2n 只,围成一圈,吃1隔1,留下的是第2n 只(最后一只).【问题7】(吃1隔1)猫将9只老鼠围成一圈,先吃1只,再隔1只,再吃1只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.从这里开始,我们要用“化归思想”——将新问题转化为已解决问题——来处理猫吃老鼠问题:总数为2n 的情况我们已经解决过了,那么如果我们考虑让猫吃掉1只,剩下8只时的最后一只,不就是我们要找的答案吗?马上尝试一下:吃掉1、跳过2,此时还有8只老鼠,3是新的起点,那么刚跳过的2就是此时的最后一只了,于是答案为第2只.更一般的情况同样可以归纳出来:(1)总数为M 时,先吃掉一些老鼠使总数变为2n ,也就是要吃掉(2)n M −只. (在这里,为了计算方便,我们希望吃掉的老鼠尽量少,也就意味着2n 要尽量大)(2)找出此时的最后一只老鼠,它正是最后一个“吃-跳”周期中跳过的一只. 由于每2只老鼠为一个“吃-跳”周期,所以最后跳过的是第2(2)n M −只老鼠. 结论:M 只,围成一圈,吃1隔1,留下的是第2(2)n M −只.(n 取范围内最大)【问题8】(隔1吃1)猫将9只老鼠围成一圈,先隔1只,再吃1只,再隔1只,再吃1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.继续用化归思想:“从1开始,先隔再吃”,不就是“从2开始的先吃后隔”吗?于是,我们仍然可以按照【问题7】的方法进行计算,最后编号加1就可以了.也就是说,答案是第 32(92)13−+=只.这里还有一个神奇的二进制方法:将总数改写为二进制形式:29(1001)=,将最高位的1移动到最右边,马上可以得到正确答案(0011)3=了!2这个“数位操作”法的原理是这样的:将最高位的1移走时,相当于原数减去了最大的2n;将1放到最右边时,其余各数全部左移一位,即数值乘2;最低位多了1,相当于给数值加1.这正是结论中的2(2)1nM−+!由于这一方法只需对二进制表达进行位移操作,在编程上很容易实现,所以正是计算机解决约瑟夫斯问题的快速算法.【问题9】(吃2隔1)(1)猫将9只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将18只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.实际操作发现,(1)的答案为9,(2)的答案为18.也就是说,这个结果也是与直线型完全一致的.与【问题6】类似,我们可以得出结论:结论:3n或23n×只(最后一只).×只,围成一圈,吃2隔1,留下的是第3n或23n【问题10】(吃2隔1)(1)猫将19只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.(2)猫将20只老鼠围成一圈,先吃2只,再隔1只,再吃2只,再隔1只,……直到最后剩下1只老鼠,这只老鼠是原来的第________只.仿照【问题7】的“化归思想”,如果提前吃掉一部分老鼠,把总数变为3n或23n×,问题将迎刃而解.问题是,应该将总数变成哪一种呢?这里的化归,不仅要看到总数,而且要保证余下的老鼠依然是“吃2隔1”的周期.所以,我们先吃掉、跳过的部分,也必须是整数个“吃2隔1”周期.换句话说,提前吃掉的老鼠总数,必须是一个偶数.那么上述两道题的做法应该是这样的:(1)19只老鼠:19是奇数,吃掉偶数只老鼠,结果为奇数,所以应化归为奇数型即9个.先吃掉19910÷=(个)周期.−=(只),每个周期吃2隔1,即进行了1025此时,最后跳过的老鼠是第5315×=(只),因此答案为第15只.(2)20只老鼠:20是偶数,吃掉偶数只老鼠,结果为偶数,所以应化归为偶数型即18个.先吃掉20182÷=(个)周期.−=(只),每个周期吃2隔1,即进行了221此时,最后跳过的老鼠是第133×=(只),因此答案为第3只.那么,一般结论也可以类似归纳出来了:结论:M只,围成一圈,吃2隔1,若M为奇数,留下的是第(3)23nM−÷×只.(n取范围内最大)若M为偶数,留下的是第(23)23nM−×÷×只.(n取范围内最大)当然,我们可以将类似的结论推广至”封闭型的吃k隔1”问题.本文篇幅有限,在此不作赘述.感兴趣的读者可以尝试自证,深入探究!。

约瑟夫问题

约瑟夫问题

4、狐狸捉兔子
题目:
围绕着山顶有10个洞,狐狸要吃兔子,兔子说:“可以,但必须找到我,我就藏身于这十个洞中,你从10号洞出发,先到1号洞找,第二次隔1个洞找,第三次隔2个洞找,以后如此类推,次数不限。”但狐狸从早到晚进进出出了1000次,仍没有找到兔子。问兔子究竟藏在哪个洞里?
参考程序下载
--------------------------------------------------------------------------------
5、进制转换
题目:
将一个十进制自然数转换成二进制数,一般采取除2取余法。从键盘输入一个十进制自然数(约定该数小于等于Maxlongint),输出相应的二进制数
设有一天平,可用来称物体的质量,同时给出一个正整数n(n<=10)。
问题1:试设计n个砝码的质量,用它们能称出尽可能多的1,2,3,...连续整数质量,约定砝码可以放在天平的左右两个托盘中的任何一个或两个中。例如n=2,此时设计2个砝码的质量分别为1,3,则能同时称取1,2,3,4。
问题2:在给出n个砝码能称出最大质量范围内的一个质量x,试给出称取x的方案。如上例中:
6/7,7/8,1/1
编程求出n级法雷序列,每行输出10个分数。n的值从键盘输入。
--------------------------------------------------------------------------------
13、砝码设计
题目:
①把组成这个四位数的4个数字由小到大排列,形成由这四个数字组成的最大的四位数;
②把组成这个四位数的4个数字由大到小排列,形成由这四个数字组成的最小的四位数(如果含有数字0,则不足四位);

线段树-约瑟夫问题 题+题解

线段树-约瑟夫问题 题+题解

Wikioi : 1282 约瑟夫问题题目描述Description有编号从1到N的N个小朋友在玩一种出圈的游戏。

开始时N个小朋友围成一圈,编号为I+1的小朋友站在编号为I小朋友左边。

编号为1的小朋友站在编号为N的小朋友左边。

首先编号为1的小朋友开始报数,接着站在左边的小朋友顺序报数,直到数到某个数字M时就出圈。

直到只剩下1个小朋友,则游戏完毕。

现在给定N,M,求N个小朋友的出圈顺序。

输入描述Input Description唯一的一行包含两个整数N,M。

(1<=N,M<=30000)输出描述Output Description唯一的一行包含N个整数,每两个整数中间用空格隔开,第I个整数表示第I个出圈的小朋友的编号。

分析:由题意也可以看出,算法明确的分为两部分:模拟约瑟夫+ 求取原始序号。

1. 约瑟夫模拟:使用相对坐标(相对于环内),例如当前被踢位置为k,下一个被踢的是+m,则k被踢掉以后,原本的k+1就成了k(相对坐标嘛)。

这样下一个位置就应该是新K + m-1 。

再考虑到循环意义:next = (k + m-1 - 1) % 人数+ 1。

当m为负向时,同理,只是要注意保证求得的下一次位置值是个正值。

2. 取原始序号:这个直接看下面的代码( update() )Code:#include <iostream>#include <cstdio>using namespace std;#define LL long longconst int N = 30001*4;#define lson l,m,rt<<1#define rson m+1,r,rt<<1|1int sum[N<<2];int tree[N<<2][2];void PushUp(int rt){sum[rt] = sum[rt<<1] + sum[rt<<1|1];}void build(int l,int r,int rt){tree[rt][0] = l;tree[rt][1] = r;if(l == r){sum[rt] = 1;return;}int m = (l+r)>>1;build(lson);build(rson);PushUp(rt);}int update(int p,int rt){sum[rt] --;if(tree[rt][0] == tree[rt][1]){sum[rt] = 0;return tree[rt][0];//从绝对位置剔除}if(p <= sum[rt<<1]) return update(p,rt<<1);else return update(p-sum[rt<<1],rt<<1|1);PushUp(rt);}int main(){int n,m;scanf("%d%d",&n,&m);build(1,n,1);int pos = 1;int seq = 1;for(int i = 0 ; i < n ; i++){seq = (seq + m - 1) % sum[1];//seq 只是相对位置if(seq == 0) seq = sum[1];pos = update(seq,1);cout<<pos<<" ";}return 0;}。

约瑟夫问题详解(CC++)

约瑟夫问题详解(CC++)Josephus 约瑟夫问题假设n个竞赛者排成一个环形,依次顺序编号1,2,…,n。

从某个指定的第1号开始,沿环计数,每数到第m个人就让其出列,且从下一个人开始重新计数,继续进行下去。

这个过程一直进行到所有的人都出列为止。

最后出列者为优胜者。

无论是用链表实现还是用数组实现来解约瑟夫问题都有一个共同点:要模拟整个游戏过程,不仅程序写起来比较麻烦,而且时间复杂度高达O(nm),当n,m非常大(例如上百万,上千万)的时候,几乎是没有办法在短时间内出结果的。

注意到原问题仅仅是要求出最后的胜利者的序号,而不是要模拟整个过程。

因此如果要追求效率,就要打破常规,实施一点数学策略。

为了讨论方便,先把问题稍微改变一下,并不影响原意:问题描述:n个人(编号0~(n-1)),从0开始报数,报到(m-1)的退出,剩下的人继续从0开始报数。

求胜利者的编号。

我们知道第一个人(编号一定是m%n-1) 出列之后,剩下的n-1个人组成了一个新的约瑟夫环(以编号为k=m%n的人开始): k k+1 k+2 ... n-2, n-1, 0, 1, 2, ... k-2并且从k开始报0。

现在我们把他们的编号做一下转换:k --> 0k+1 --> 1k+2 --> 2......k-2 --> n-2变换后就完完全全成为了(n-1)个人报数的子问题,假如我们知道这个子问题的解:例如x 是最终的胜利者,那么根据上面这个表把这个x变回去不刚好就是n个人情况的解吗?变回去的公式很简单:x'=(x+k)%n如何知道(n-1)个人报数的问题的解?显然,只要知道(n-2)个人的解就行了。

(n-2)个人的解呢?当然是先求(n-3)的情况---- 这显然就是一个倒推问题!递推公式:令f[i]表示i个人玩游戏报m退出最后胜利者的编号,最后的结果自然是f[n]递推公式f[1]=0;f[i]=(f[i-1]+m)%i; (i>1)有了这个公式,我们要做的就是从1-n顺序算出f[i]的数值,最后结果是f[n]。

约瑟夫问题

约瑟夫问题约瑟夫问题是个有名的问题:N个人围成一圈,从第一个开始报数,第M个将被杀掉,最后剩下一个,其余人都将被杀掉。

例如N=6,M=5,被杀掉的人的序号为5,4,6,2,3。

最后剩下1号。

假定在圈子里前K个为好人,后K个为坏人,你的任务是确定这样的最少M,使得所有的坏人在第一个好人之前被杀掉。

举个例子:有64名战士被敌人俘虏了。

敌人命令他们拍成一圆圈,编上号码1,2,3…,64。

敌人把1号杀了,又把3号杀了,他们隔着一个杀一个这样转着圈杀。

最后只剩下一个人,这个人就是约瑟夫斯。

请问约瑟夫斯是多少号?(这就是“约瑟夫斯”问题。

)这个问题解答起来比较简单:敌人从1号开始,隔一个杀一个,第一圈把所有的奇数号码的战士圈杀光了。

剩下的32名战士需要重新编号,而敌人在第二圈杀死的是重新编号的奇数号码。

由于第一圈剩下的全部是偶数号2,4,6,…,64。

把它们全部用2去除,得1,2,3,…,32。

这是第二圈编的号码。

第二圈杀过以后,又把奇数号码都杀掉了,还剩16个人。

如此下去,可以想到最后剩下的必然是64号。

$64=2^6$,它可以连续被2整除6次,是从1到64中能被2整除次数最多的数,因此,最后必然把64 号留下。

如果有65名战士被俘,敌人还是按上述的方法残杀战士,最后还会剩下约瑟夫斯吗?经过计算,很容易得到结论,不是。

因为第一个人被杀后,也就是1号被杀后,第二个被杀的是必然3号。

如果把1号排除在外,那么还剩下的仍是64人,新1号就是3号。

这样原来的2号就变成了新的64 号,所以剩下的必然是2号。

进一步的归类,不难发现如果原来有$2^k$个人,最后剩下的必然$2^k$号;如果原来有$2^k+1$个人,最后剩下2号;如果原来有$2^k+2$个人,最后剩下4号……如果原来有$2^k+m$个人,最后剩下2m号.比如:原来有100人,由于$100=64+36=2^6+36$,所以最后剩下的就是36×2=72号;又如:原来有11 1人,由于$100=64+47=2^6+47$,所以最后剩下的就是47×2=94号传说古代有一批人被蛮族俘虏了,敌人命令他们排成圆圈,编上号码1,2,3,…然后把1号杀了,把3号杀了,总之每隔一个人杀一个人,最后剩下一个人,这个人就是约瑟夫斯。

约瑟夫问题





第一个人出列:从 数器 j 从 1 数到 person[i] <- 0 第二个人出列:从 数器 j 从 1 数到 person[i] <- 0 „„ n-1个出列:„„
k = 0 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
k = i 的后继开始扫描数组,报数计 m,该人出列,把他的状态置成 0,
此题如果意思是定位第 m 个元素,当不需要跳过任何元素 时。 从 k 开始报数第 m 个元素是: ((( k + m - 1 ) – 1 ) mod ) n +1
“遍历”和“数到”的区别: 遍历到的每个元素需要做相同的操作,而“数到m”第m个 元素和前 m-1 个元素有不同的操作。
如果需要跳过某些标记的元素,就无法直接确定第 m 个元 素的位置,则需要按顺序逐个扫描元素,跳过不符合要求的 元素,对符合要求的元素进行计数。 此处注意第一个数也许需要跳过,第一个报数的编号也许不 是 k,不能直接从 1 计数。 i <- k j <- 0 while ( j< m ) do begin i <- (i mod n) + 1 if ( person[i] <> 0 ) then j <- j+1 end

2.1
模拟方法
2.2
2.1.1 数组+标记 2.1.2 循环链表+删除节点
数学模型



定义数组: array person[1..n]代表 n 个人的状态; 第i个元素代表编号为i的人的状态,下标代表人的编号; 状态: 1代表没有出列,0代表已出列。 使用线性数组表示环状圆圈 * 数组元素间相邻关系和环状结构元素间相邻关系有所不 同,person[n]没有后继节点,person[1]没有前驱节点。 数组可以使用下面方法实现环状结构的相邻关系: person[i]后继节点的下标为j: if i = n then i<- 1 else i <- i+1 或者 i <- (i mod n) +1

约瑟夫斯问题

约瑟夫斯问题约瑟夫问题维基百科,⾃由的百科全书跳到导航跳到搜索约瑟夫问题(有时也称为约瑟夫斯置换),是⼀个出现在计算机科学和数学中的问题。

在计算机编程的算法中,类似问题⼜称为约瑟夫环。

⼈们站在⼀个等待被处决的圈⼦⾥。

计数从圆圈中的指定点开始,并沿指定⽅向围绕圆圈进⾏。

在跳过指定数量的⼈之后,处刑下⼀个⼈。

对剩下的⼈重复该过程,从下⼀个⼈开始,朝同⼀⽅向跳过相同数量的⼈,直到只剩下⼀个⼈,并被释放。

问题即,给定⼈数、起点、⽅向和要跳过的数字,选择初始圆圈中的位置以避免被处决。

历史这个问题是以弗拉维奥·约瑟夫命名的,他是1世纪的⼀名犹太历史学家。

他在⾃⼰的⽇记中写道,他和他的40个战友被罗马军队包围在洞中。

他们讨论是⾃杀还是被俘,最终决定⾃杀,并以抽签的⽅式决定谁杀掉谁。

约瑟夫斯和另外⼀个⼈是最后两个留下的⼈。

约瑟夫斯说服了那个⼈,他们将向罗马军队投降,不再⾃杀。

约瑟夫斯把他的存活归因于运⽓或天意,他不知道是哪⼀个。

[1]解法⽐较简单的做法是⽤循环单链表模拟整个过程,时间复杂度是O(n*m)。

如果只是想求得最后剩下的⼈,则可以⽤数学推导的⽅式得出公式。

且先看看模拟过程的解法。

Python版本-- coding: utf-8 --class Node(object):def init(self, value):self.value = valueself.next = Nonedef create_linkList(n):head = Node(1)pre = headfor i in range(2, n+1):newNode = Node(i)pre.next= newNodepre = newNodepre.next = headreturn headn = 5 #总的个数m = 2 #数的数⽬if m == 1: #如果是1的话,特殊处理,直接输出print (n)else:head = create_linkList(n)pre = Nonecur = headwhile cur.next != cur: #终⽌条件是节点的下⼀个节点指向本⾝for i in range(m-1):pre = curcur = cur.nextprint (cur.value)pre.next = cur.nextcur.next = Nonecur = pre.nextprint (cur.value)using namespace std;typedef struct _LinkNode {int value;struct _LinkNode* next;} LinkNode, *LinkNodePtr;LinkNodePtr createCycle(int total) {int index = 1;LinkNodePtr head = NULL, curr = NULL, prev = NULL;head = (LinkNodePtr) malloc(sizeof(LinkNode));head->value = index;prev = head;while (--total > 0) {curr = (LinkNodePtr) malloc(sizeof(LinkNode));curr->value = ++index;prev->next = curr;prev = curr;}curr->next = head;return head;}void run(int total, int tag) {LinkNodePtr node = createCycle(total);LinkNodePtr prev = NULL;int start = 1;int index = start;while (node && node->next) {if (index == tag) {printf("%d\n", node->value);prev = node->next;node->next = NULL;node = prev;} else {prev->next = node->next;node->next = NULL;node = prev->next;}index = start;} else {prev = node;node = node->next;index++;}}}int main() {if (argc < 3) return -1;run(atoi(argv[1]), atoi(argv[2]));return 0;}数学推导解法我们将明确解出{\displaystyle k=2}k=2时的问题。

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

实验一:约瑟夫斯问题求解一、问题描述1. 实验题目:约瑟夫斯问题的一种描述是:编号为1,2,……n的n个人按顺时针方向围坐一圈,每人持有一个密码9(正整数)。

一开始任选一个正整数作为报数上限值m,从第一个人开始按照顺时针方向自1开始报数,报到m时停止报数,报m的人出列,将他的密码作为新的m值,从他在顺时针方向下一个人开始重新重新从1报数,如此下去,直至所有的人全部出列为止。

试设计一个程序,按出列顺序印出各人编号。

2. 基本要求:利用单向循环链表模拟此过程,按照出列的顺序印出各人编号。

3. 测试数据:n=7,7个人的密码依次为3,1,7,2,4,8,4.m的初始值为6(正确的出列顺序应为6,1,4,7,2,3,5。

二、需求分析1.程序所能达到的基本可能:本程序能够按出列顺序印出各人编号,程序运行后显示提示信息,提示用户输入人数n,初始报数值m,n个人的密码,程序需自动考虑重复的,用户输入完毕后,程序自动输出运算结果。

2. 输入的形式及输入值范围:输入人数n,初始报数值m,n个人的密码,所有值均为正整数int型。

3. 输出的形式:输出的是按出列顺序印出各人编号,为正整数int型。

4.测试数据要求:测试数据要求为int型三、概要设计1.所用到得数据结构及其ADTﻩ为了实现上述功能,应以单向循环链表有序链表表示集合数据类型。

1.单向循环链表抽象数据类型定义:typedef struct node{ElemType data;ElemType num;struct node *next;}SLNODE;基本操作:struct node *create_sl(int n);//创建单向循环链表2.主程序流程及其模块调用关系1)创建循环链表的流程图2)约瑟夫问题求解流程图3)主函数流程图四、详细设计1. 实现每个操作的伪码,重点语句加注释主程序:void main(){SLNODE *head;int n,m;head=(SLNODE *)malloc(sizeof(SLNODE));printf("/*************************************/\n"); //初始界面printf(" 学号:031350102\n");printf("姓名:王亚文\n");printf("约瑟夫斯问题求解\n");printf("/*************************************/\n");printf("输入总人数n=\n");scanf("%d",&n);printf("输入初始报数值m=\n");scanf("%d",&m);head=create_sl(n);Josephus(head,n,m);}2.创建循环单链表struct node *create_sl(int n){SLNODE *p,*s,*head;ElemType x;int a;head=(SLNODE *)malloc(sizeof(SLNODE));p=head;head->next=head;for(a=1;a<=n;a++) //循环直到输入n个密码值跳出循环 {s=(SLNODE *)malloc(sizeof(SLNODE));printf("请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}p->next=head;return head;}3.约瑟夫斯问题求解:void Josephus(SLNODE*head,int n,intm){SLNODE *p,*q;int i;p=head;printf("出列序列:");while(p->next!=p){for(i=1;i<m;i++) //报数报的m时跳出循环{q=p;p=p->next;}m=p->data;//读取新的密码值printf("%4d",p->num);q->next=p->next;//删除p节点free(p);p=q->next;}printf("%4d\n",p->num);}4. 函数调用关系图主函数中调用了struct node *create_sl(int n);void Josephus(SLNODE *head,int n,int m);两个函数五、调试分析1. 设计与调试过程中遇到的问题分析、体会1)创建单链表时,一开始写的程序是void create_sl(SLNODE *head ,int n),并没有没有报错,但最后运行时却不是想象的结果,然后尝试在主函数中写一个printf函数看一下创建表是否创建成功,事实证明并没有,后来改成了struct node*create_sl(int n);解决了这个问题,再次就是建表的时候发现最后一个数并不是我输入的数,然后就是开始改那个循环=函数,发现我虽然是读了7个数,但第7个数并没有赋值给链表,原错误函数:p=head;head->next=head;printf("请输入密码值\n");scanf("%d",&x);for(a=1;a<n;a++) //循环直到输入n个密码值跳出循环{s=(SLNODE *)malloc(sizeof(SLNODE));printf("请输入密码值\n");scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}经过修正后的函数:p=head;head->next=head;for(a=1;a<=n;a++) //循环直到输入n个密码值跳出循环{s=(SLNODE*)malloc(sizeof(SLNODE));printf("请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}2)建表成功之后开始解决本次的主问题约瑟夫斯求解问题,本问题主要考虑循环终止条件,一开始写的是head->next=head;发现经常运行错误,后来修正用p->next!=p,然后最后一个p值单独写一句输出printf("%4d\n",p->num);中间的句子就是找到报数值然后删除,注意保留要删除节点的密码值,并没有什么大问题。

还有一个问题,就是在开始的时候创建单链表并没有想到要用序号值num,最开始定义单链表的语句:typedef structnode{ElemType data;struct node *next;}SLNODE; 然后就会在创建链表赋值时和解决约瑟夫斯问题时都要重新定义一个变量x进行计数,增加了程序的复杂度最后修正为:typedef struct node{ElemType data;ElemType num;struct node *next;}SLNODE;3)剩下的还有一些小问题,比如少打了一个字母,打错一个字母,这些程序会报错,不属于逻辑错误,所以解决起来也比较快,2. 主要算法的时间复杂度分析创建单链表的时间复杂度为O(n);约瑟夫斯问题的时间复杂度与n值有关,也与每个人的密码值有关,时间复杂度O(mn);空间复杂度为O(n);六、使用说明程序运行后显示提示信息,提示用户输入人数n,初始报数值m,n个人的密码,用户按照提示输入完毕后,程序自动输出运算结果。

七、测试结果八、附录#include <stdio.h>#include <malloc.h>#include <stdlib.h>#include <time.h>typedef int ElemType;typedef struct node{ElemTypedata;ElemType num;struct node *next;}SLNODE; //单链表的定义struct node *create_sl(int n);void Josephus(SLNODE *head,intn,int m);int main() //主函数{SLNODE *head;int n,m;time_t rawtime;struct tm * timeinfo;time (&rawtime);timeinfo = localtime (&rawtime);head=(SLNODE *)malloc(sizeof(SLNODE));printf("/*************************************/\n"); //初始界面printf(" 学号:031350102\n");printf(" 姓名:王亚文\n");printf(" 约瑟夫斯问题求解\n");printf("/*************************************/\n");printf("输入总人数n=\n");scanf("%d",&n);printf("输入初始报数值m=\n");scanf("%d",&m);head=create_sl(n); //创建单链表Josephus(head,n,m);printf("Current local time anddate: %s", asctime(timeinfo)); return 0;}struct node *create_sl(int n){SLNODE *p,*s,*head;ElemTypex;int a;head=(SLNODE*)malloc(sizeof(SLNODE));p=head;head->next=head;for(a=1;a<=n;a++) //循环直到输入n个密码值跳出循环{s=(SLNODE *)malloc(sizeof(SLNODE));printf("请输入第%d个人的密码值\n",a);scanf("%d",&x);s->data=x;s->num=a;if(head->next==head) head=s;else p->next=s;p=s;}p->next=head;return head;}void Josephus(SLNODE *head,int n,int m)//约瑟夫斯问题求解{SLNODE *p,*q;int i;p=head;printf("出列序列:");while(p->next!=p){for(i=1;i<m;i++)//程序运行到第m个人跳出循环{q=p;p=p->next;}m=p->data; //读取新的密码值printf("%4d",p->num);q->next=p->next;//删除p节点free(p);p=q->next;}printf("%4d\n",p->num);}九、实验收获和感想通过本实验首先是解决了约瑟夫斯问题,利用计算机快速解决这个问题,其次在程序编写过程中慢慢掌握了循环单链表的一些用法以及注意事项,因为距离上次学习c语言已经有很长一段时间,这次确实遇到了很多障碍,不仅仅是对链表的生疏,也包含以前基础的薄弱环节,通过一个程序的设计,慢慢回忆起程序的编写,加强自己的逻辑性,编程是一个很麻烦的事,一个小错误的出项,程序就不会出现预想的结果,如果报错还可以按照提示找到,若有逻辑错误还得一行一行检查,最后发现如果程序需要解决多个问题,最好还是分模块解决,解决完一个问题再去解决另一个问题,如果一个模块有错误及时发现,也比较好改。

相关文档
最新文档