算法与程序实践习题解答5模拟.docx
- 文档编号:10485930
- 上传时间:2023-02-13
- 格式:DOCX
- 页数:92
- 大小:128.10KB
算法与程序实践习题解答5模拟.docx
《算法与程序实践习题解答5模拟.docx》由会员分享,可在线阅读,更多相关《算法与程序实践习题解答5模拟.docx(92页珍藏版)》请在冰豆网上搜索。
算法与程序实践习题解答5模拟
《算法与程序实践》习题解答5——模拟
现实中的有些问题,难以找到公式或规律来解决,只能按照一定步骤,不停地做下去,最后才能得到答案。
这样的问题,用计算机来解决十分合适,只要能让计算机模拟人在解决此问题的行为即可。
这一类的问题可以称之为“模拟题”。
比如下面经典的约瑟夫问题:
讲课:
CS51CS52CS53
实验:
CS56CS516CS517
讲课:
CS59CS513CS518
CS51:
约瑟夫问题
(来源:
2746,程序设计导引及在线实践(李文新)例6.1P141)
问题描述:
约瑟夫问题:
有n只猴子,按顺时针方向围成一圈选大王(编号从1到n),从第1号开始报数,一直数到m,数到m的猴子退出圈外,剩下的猴子再接着从1开始报数。
就这样,直到圈内只剩下一只猴子时,这个猴子就是猴王,编程求输入n,m后,输出最后猴王的编号。
输入:
每行是用空格分开的两个整数,第一个是n,第二个是m(0 最后一行是: 00 输出: 对于每行输入数据(最后一行除外),输出数据也是一行,即最后猴王的编号。 样例输入: 62 124 83 00 样例输出: 5 1 7 解题思路: 初一看,很可能想把这道题目当作数学题来做,即认为结果也许会是以n和m为自变量的某个函数f(n,m),只要发现这个函数,问题就迎刃而解。 实际上,这样的函数很难找,甚至也许根本就不存在。 用人工解决的办法就是将n个数写在纸上排成一圈,然后从1开始数,每数到第m个就划掉一个数,一遍遍做下去,直到剩下最后一个。 有了计算机,这项工作做起来就会快多了,我们只要编写一个程序,模拟人工操作的过程就可以了。 用数组anLoop来存放n个数,相当于n个数排成的圈;用整型变量nPtr指向当前数到的数组元素,相当于人的手指;划掉一个数的操作,就用将一个数组元素置0的方法来实现。 人工数的时候,要跳过已经被划掉的数,那么程序执行的时候,就要跳过为0的数组元素。 需要注意的是,当nPtr指向anLoop中最后一个元素(下标n-1)时,再数下一个,则nPtr要指回到数组的头一个元素(下标0),这样anLoop才象一个圈。 参考程序: #include #include #defineMAX_NUM300 intaLoop[MAX_NUM+1]; intmain() { intn,m,i; while (1) { scanf("%d%d",&n,&m); if(n==0)break; for(i=0;i aLoop[i]=i+1; intnPtr=0;//存储位置信息 for(i=0;i { intnCount=0;//记录本轮数到的猴子数目 while(nCount { while(aLoop[nPtr]==0)//跳过已经出圈的猴子 nPtr=(nPtr+1)%n;//到下一个位置,如果到最后就跳到第1个 nCount++; nPtr=(nPtr+1)%n; } nPtr--;//找到要出圈的猴子,位置要回退一个 if(nPtr<0) nPtr=n-1; if(i==n-1)//最后一个出圈的猴子 printf("%d\n",aLoop[nPtr]); aLoop[nPtr]=0; } } return0; } 注意事项: 上面的程序完全模拟了人工操作的过程,但因为要反复跳过为0的数组元素,因此算法的效率不是很高。 后文的“链表”一章,采用单链表进行模拟来解决本题,就能省去跳过已出圈的猴子这个操作,大大提高了效率。 n个元素的数组,从下标0的元素开始存放猴子编号,则循环报数的时候,下一个猴子的下标就是“(当前猴子下标+1)%n”。 这种写法比用分支语句来决定下个猴子的下标是多少,更快捷而且写起来更方便。 对于本题,虽然很难直接找出结果函数f(n,m),但是如果仔细研究,找出局部的一些规律,比如,每次找下一个要出圈的猴子时,直接根据本次的起点位置就用公式算出下一个要出圈的猴子的位置,那么写出的程序就可以省去数m只猴子这个操作,大大提高效率,甚至不需要用数组来存放n个数。 请写出这个高效而节省空间的程序。 问题一: 在数组里循环计数的时候,一定要小心计算其开始的下标和终止的下标。 比如,语句15,循环是从0到n-1,而不是从0到n。 问题二: nPtr--到nPtr=n-1回退一个位置,易被忽略或写错。 比如只写了语句nPtr--,忘了处理nPtr变成小于0的情况。 CS52: 花生问题(同CS93) (来源: 2950,程序设计导引及在线实践(李文新)例4.3P107) 问题描述: 鲁宾逊先生有一只宠物猴,名叫多多。 这天,他们两个正沿着乡间小路散步,突然发现路边的告示牌上贴着一张小小的纸条: “欢迎免费品尝我种的花生! ——熊字”。 鲁宾逊先生和多多都很开心,因为花生正是他们的最爱。 在告示牌背后,路边真的有一块花生田,花生植株整齐地排列成矩形网格(如图5-1)。 有经验的多多一眼就能看出,每棵花生植株下的花生有多少。 为了训练多多的算术,鲁宾逊先生说: “你先找出花生最多的植株,去采摘它的花生;然后再找出剩下的植株里花生最多的,去采摘它的花生;依此类推,不过你一定要在我限定的时间内回到路边。 ” 我们假定多多在每个单位时间内,可以做下列四件事情中的一件: 1)从路边跳到最靠近路边(即第一行)的某棵花生植株; 2)从一棵植株跳到前后左右与之相邻的另一棵植株; 3)采摘一棵植株下的花生; 4)从最靠近路边(即第一行)的某棵花生植株跳回路边。 图5-1花生地图5-2摘花生过程 现在给定一块花生田的大小和花生的分布,请问在限定时间内,多多最多可以采到多少个花生? 注意可能只有部分植株下面长有花生,假设这些植株下的花生个数各不相同。 例如在图5-2所示的花生田里,只有位于(2,5),(3,7),(4,2),(5,4)的植株下长有花生,个数分别为13、7、15、9。 沿着图示的路线,多多在21个单位时间内,最多可以采到37个花生。 输入: 输入的第一行包括三个整数,M,N和K,用空格隔开;表示花生田的大小为M*N(1<=M,N<=20),多多采花生的限定时间为K(0<=K<=1000)个单位时间。 接下来的M行,每行包括N个非负整数,也用空格隔开;第i+1行的第j个整数Pij(0<=Pij<=500)表示花生田里植株(i,j)下花生的数目,0表示该植株下没有花生。 输出: 输出包括一行,这一行只包含一个整数,即在限定时间内,多多最多可以采到花生的个数。 样例输入: 6721 0000000 00001300 0000007 01500000 0009000 0000000 样例输出: 37 解题思路: 试图找规律,得到一个以花生矩阵作为自变量的公式来解决这个问题,是不现实的。 结果只能是做了才知道。 即走进花生地,每次要采下一株花生之前,先计算一下,剩下的时间,够不够走到那株花生,采摘,并从那株花生走回到路上。 如果时间够,则走过去采摘;如果时间不够,则采摘活动到此结束。 参考程序: #include #include #include #include intT,M,N,K; #defineMAX_NUM55 intaField[MAX_NUM][MAX_NUM]; intmain() { inti,j,t,m,n; scanf("%d",&T); for(t=0;t { scanf("%d%d%d",&M,&N,&K); for(m=1;m<=M;m++) for(n=1;n<=N;n++) scanf("%d",&aField[m][n]); intnTotalPeanuts=0;//摘得的花生总数 intnTotalTime=0;//已经花去的总时间 intnCuri=0,nCurj;//当前位置坐标 while(nTotalTime { intnMax=0,nMaxi,nMaxj;//最大的花生数目,及其所处的位置 //寻找下一个最大花生数目及其位置 for(i=1;i<=M;i++) { for(j=1;j<=N;j++) { if(nMax { nMax=aField[i][j]; nMaxi=i; nMaxj=j; } } } if(nMax==0)//地里没有花生了 break; if(nCuri==0) nCurj=nMaxj;//如果当前位置在路上,那么应走到横坐标nMaxj处,再进入花生地 //下面检查剩余的时间够不够走到nMaxi,nMaxj处,摘取花生,并回到路上 if(nTotalTime+nMaxi+1+abs(nMaxi-nCuri)+abs(nMaxj-nCurj)<=K) { nTotalTime+=1+abs(nMaxi-nCuri)+abs(nMaxj-nCurj); nCuri=nMaxi; nCurj=nMaxj; nTotalPeanuts+=aField[nMaxi][nMaxj]; aField[nMaxi][nMaxj]=0;//摘走花生赋值为0 } else break; } printf("%d\n",nTotalPeanuts); } return0; } 实现技巧: 用二维数组存放花生地的信息是很自然的想法。 然而,用aField[0][0]还是aField[1][1]对应花生地的左上角,是值得思考一下的。 因为从地里到路上还需要1个单位时间,题目中的坐标又都是从1开始,所以若aField[1][1]对应花生地的左上角,则从aField[i][j]点,回到路上所需时间就是i,这样更为方便和自然,不易出错。 并不是C/C++的数组下标从0开始,我们使用数组的时候,就要从下标为0的元素开始用。 常见问题: 问题一: 读题时应该仔细读。 有的同学没有看到每次只能拿剩下花生株中最大的,而是希望找到一种在规定时间内能够拿最多花生的组合,把题目变成了另外一道题。 问题二: 有的同学没有读到“没有两株花生株的花生数目相同”的条件,因此把题目复杂化了。 问题三: 这个题目是假设猴子在取花生的过程中不会回到大路上的,有些同学在思考是否可能在中间回到大路上,因为题目没说在大路上移动要花时间,所以有可能中途出来再进去摘的花生更多。 CS53: 显示器(见CS327) (来源: 2745,程序设计导引及在线实践(李文新)例6.3P147) 问题描述: 你的一个朋友刚买了一台新电脑,之前,他用过的最好的电脑只能是便携式计数器。 现在,你的朋友看着他的新电脑,他很失望,因为他喜欢他的计数器的LC显示器。 因此你决定给你的朋友编写一个程序模拟LC显示器来显示数字。 输入: 输入文件包含多行,每一行为需要显示的数。 每一行中有两个整数s和n,1<=s<=10,0<=n<=99999999。 n为要显示的数值,s是显示的大小。 输入文件的最后一行为两个0,这一行不需要处理。 输出: 以LC显示器方式输出输入文件中的数,用符号“-”表示水平的线段,用符号“|”表示垂直的线段。 数值中的每个数字占s+2列,2s+3行。 在输出时,对每两个数字之间的空白区域,要确保用空格填满,对最后一位数字之后的空白区域,不能输出空格。 每两个数字之间仅有一个空列。 (样例输出中给出了0~9每个数字的输出格式) 每个数值之后输出一个空行。 样例输入: 212345 367890 00 样例输出: ------ |||||| |||||| -------- ||||| ||||| ------ --------------- |||||||| |||||||| |||||||| --------- |||||||| |||||||| |||||||| ------------ 解题分析: 一个计算器上的数字显示单元,可以看作由以下编号从1到7的7个笔画组成: 图5-3显示单元的笔画 那么,我们可以说,数字8覆盖了所有的笔画,数字7覆盖笔画1、3和6,而数字1覆盖笔画3、6。 注意,每个笔画都是由s个’-‘或s个’|’组成。 输出时,先输出第1行,即整数n中所有数字里的笔画1,然后输出第2行到第s+1行,即所有数字的笔画2和笔画3,接下来是第s+2行,即所有数字的笔画4,再接下来是第s+3行到2×s+2行,,就是所有数字的笔画5和笔画6,最后的第2×s+3行,是所有数字的笔画7。 如果某个数字d没有覆盖某个笔画m(m=1…7),那么,输出数字d的笔画m的时候,就应该都输出空格;如果覆盖了笔画m,则输出s个’-‘或s个’|’,这取决于笔画m是横的还是竖的。 由上思路,解决这道题目的关键,就在于如何记录每个数字都覆盖了哪些笔画。 实际上,如果我们记录的是每个笔画都被哪些数字覆盖,则程序实现起来更为容易。 一个笔画被哪些数字所覆盖,可以用一个数组来记录,比如记录笔画1覆盖情况的数组如下: charn1[11]={"--------"}; 其中,n1[i](i=0……9)代表笔画1是否被数字i覆盖。 如果是,则n1[i]为'-',如果否,则n1[i]为空格。 上面的数组的值体现了笔画1被数字0,2,3,5,6,7,8,9覆盖。 对于竖向的笔画2,由字符'|'组成,则记录其覆盖情况的数组如下: charn2[11]={"||||||"}; 该数组的值体现了笔画2被数字0,4,5,6,8,9覆盖。 参考程序: //显示器(见CS327)2745 #include #include charn1[11]={"--------"};//笔画1被数字0,2,3,5,6,7,8,9覆盖 charn2[11]={"||||||"};//笔画2被数字0,4,5,6,8,9覆盖 charn3[11]={"||||||||"};//笔画3被数字0,1,2,3,4,7,8,9覆盖 charn4[11]={"-------"};//笔画4被数字2,3,4,5,6,8,9覆盖 charn5[11]={"||||"};//笔画5被数字0,2,6,8覆盖 charn6[11]={"|||||||||"};//笔画6被数字0,1,3,4,5,6,7,8,9覆盖 charn7[11]={"-------"};//笔画7被数字0,2,3,5,6,8,9覆盖 intmain() { ints; charszNumber[20]; intnDigit,nLength,i,j,k; while (1) { scanf("%d%s",&s,szNumber); if(s==0) break; nLength=strlen(szNumber); //输出所有数字的笔画1 for(i=0;i { if(i! =0)printf(""); nDigit=szNumber[i]-'0'; printf(""); for(j=0;j printf("%c",n1[nDigit]); printf(""); } printf("\n"); for(i=0;i { for(j=0;j { if(j! =0)printf(""); nDigit=szNumber[j]-'0'; printf("%c",n2[nDigit]); for(k=0;k printf("");//笔画2和笔画3之间的空格 printf("%c",n3[nDigit]); } printf("\n"); } for(i=0;i { if(i! =0)printf(""); printf(""); nDigit=szNumber[i]-'0'; for(j=0;j printf("%c",n4[nDigit]); printf(""); } printf("\n"); for(i=0;i { for(j=0;j { if(j! =0)printf(""); nDigit=szNumber[j]-'0'; printf("%c",n5[nDigit]); for(k=0;k printf(""); printf("%c",n6[nDigit]); } printf("\n"); } for(i=0;i { if(i! =0)printf(""); printf(""); nDigit=szNumber[i]-'0'; for(j=0;j printf("%c",n7[nDigit]); printf(""); } printf("\n"); printf("\n"); } return0; } 实现技巧: 一个笔画被哪些数字所覆盖,最直接的想法是用整型数组来记录,比如: intn1[10]={1,0,1,1,0,1,1,1,1,1};表示笔画1的被覆盖情况。 可是与其在数字i的笔画1所处的位置进行输出的时候,根据n1[i]的值决定输出空格还是'-’,还不如直接用下面的char类型数组来表示覆盖情况: charn1[11]={"--------"}; 这样,在数字i的笔画1所处的位置进行输出的时候,只要输出s个n1[i]就行了。 这是一个很好的思路,它提醒我们,以后在编程时设置一些标志的时候,要考虑一下是否可以直接用更有意义的东西将0,1这样的标志代替。 常见问题: 问题一: 没有注意到输出是按行,即先输出所有数字的第一画,再输出第二画……。 于 是想一个数字一个数字地从左到右输出,编了一阵才发现不对。 问题二: 忘了输出空格。 应把所有的空白用空格符填充。 例如: 若要输出4的话就是这样: (。 表示空格) 。 。 。 。 || || --。 。 。 。 | 。 。 。 | 。 。 。 。 问题三: 两组数据之间要加一个空行。 解题分析: 用一个三维数组把0~9共10个数字的字符形式存放起来,这个数组可以定义为digit[10][5][4],第1维10代表10个数字,第2维5代表5行,第3维4代表3列加上一个字符串结束标志。 注意: 两个数字之间有空列,但最后一个数字后没有。 另外最后一个数字后也没有任何空白区域。 参考程序(zzg): #include #include chardigit[10][5][4]={//存放0~9 {"-","||","","||","-"},//0 {"","|","","|",""},//1 {"-","|","-","|","-"},//2 {"-","|","-","|","-"},//3 {"","||","-","|",""},//4 {"-","|","-","|","-"},//5 {"-","|","-","||","-"},//6 {"-","|","","|",""},//7 {"-","||","-","||","-"},//8 {"-","||","-","|","-"}//9 }; intmain() { ints;//存储s charn[10];//用来存储n,要显示的数值 inti,j,k,m; intlen;//用来存储n的长度 //freopen("in.txt","r",stdin); //freopen("out.txt","w",stdout); while (1) { scanf("%d%s",&s,n); if(s==0)break; len=strlen(n); for(i=0;i<5;i++)//输出5行,其中第2行和第4行需要输出s倍 { if(i==1||i==3)//第2行 { for(j=1;j<=s;j++) { for(k=0;k { printf("%c",digit[n[k]-'0'][i][0]); for(m=1;m<=s;m++) printf("%c",digit[n[k]-'0'][i][1]); printf("%c",digit[n[k]-'0'][i][2]); if(k } printf("\n");
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 算法 程序 实践 习题 解答 模拟