狱龙之吻 |
2009-05-05 22:07 |
90.搬山游戏 设有n座山,计算机与人为比赛的双方,轮流搬山。规定每次搬山的数止不能超 过k座,谁搬最后一座谁输。游戏开始时。计算机请人输入山的总数(n)和每次允许搬山的最大数止(k)。然后请人开始,等人输入了需要搬走的山的数目后,计算机马上打印出它搬多少座山,并提示尚余多少座山。双方轮流搬山直到最后一座山搬完为止。计算机会显示谁是赢家,并问人是否要继续比赛。若人不想玩了,计算机便会统计出共玩了几局,双方胜负如何。 *问题分析与算法设计 计算机参加游戏时应遵循下列原则: 1) 当: 剩余山数目-1<=可移动的最大数k 时计算机要移(剩余山数目-1)座,以便将最后一座山留给人。 2)对于任意正整数x,y,一定有: 0<=x%(y+1)<=y 在有n座山的情况下,计算机为了将最后一座山留给人,而且又要控制每次搬山的数目不超过最大数k,它应搬山的数目要满足下列关系: (n-1)%(k+1) 如果算出结果为0,即整除无余数,则规定只搬1座山,以防止冒进后发生问题。 按照这样的规律,可编写出游戏程序如下: #include int main() { int n,k,x,y,cc,pc,g; printf("More Mountain Game\n"); printf("Game Begin\n"); pc=cc=0; g=1; for(;;) { printf("No.%2d game \n",g++); printf("—————————————\n"); printf("How many mpuntains are there?"); scanf("%d",&n); if(!n) break; printf("How many mountains are allowed to each time?"); do{ scanf("%d",&k); if(k>n||k<1) printf("Repeat again!\n"); }while(k>n||k<1); do{ printf("How many mountains do you wish movw away?"); scanf("%d",&x); if(x<1||x>k||x>n) /*判断搬山数是否符合要求*/ { printf("IIIegal,again please!\n"); continue; } n-=x; printf("There are %d mountains left now.\n",n); if(!n) { printf("……………I win. You are failure……………\n\n");cc++; } else { y=(n-1)%(k+1); /*求出最佳搬山数*/ if(!y) y=1; n-=y; printf("Copmputer move %d mountains away.\n",y); if(n) printf(" There are %d mountains left now.\n",n); else { printf("……………I am failure. You win………………\n\n"); pc++; } } }while(n); } printf("Games in total have been played %d.\n",cc+pc); printf("You score is win %d,lose %d.\n",pc,cc); printf("My score is win %d,lose %d.\n",cc,pc); } *思考题 取石子游戏。将石子分成若干堆,每堆有若干粒,参加游戏的甲乙两方轮流从任意一堆中取走任意个石子,甚至可以全部取走,但每次只能在一堆中取,不允许从这堆取一些,再从另一堆中取一些。直到谁取走最后一粒石子谁就获胜。请编程进行人机对弈 91.人机猜数游戏 由计算机“想”一个四位数,请人猜这个四位数是多少。人输入四位数字后,计算机首先判断这四位数字中有几位是猜对了,并且在对的数字中又有几位位置也是对的,将结果显示出来,给人以提示,请人再猜,直到人猜出计算机所想的四位数是多少为止。 例如:计算机“想”了一个“1234”请人猜,可能的提示如下: 人猜的整数 计算机判断有几个数字正确 有几个位置正确 1122 2 1 3344 2 1 3312 3 0 4123 4 0 1243 4 2 1234 4 4 游戏结束 请编程实现该游戏。游戏结束时,显示人猜一个数用了几次。 *问题分析与算法设计 问题本身清楚明了。判断相同位置上的数字是否相同不需要特殊的算法。只要截取相同位置上的数字进行比较即可。但在判断几位数字正确时,则应当注意:计算机所想的是“1123”,而人所猜的是“1576”,则正确的数字只有1位。 程序中截取计算机所想的数的每位数字与人所猜的数字按位比较。若有两位数字相同,则要记信所猜中数字的位置,使该位数字只能与一位对应的数字“相同”。当截取下一位数字进行比较时,就不应再与上述位置上的数字进行比较,以避免所猜的数中的一位与对应数中多位数字“相同”的错误情况。 *程序说明与注释 #include #include #include int main() { int stime,a,z,t,i,c,m,g,s,j,k,l[4]; /*j:数字正确的位数 k:位置正确的位数*/ long ltime; ltime=time(NULL); /*l:数字相同时,人所猜中数字的正确位置*/ stime=(unsigned int)ltime/2; srand(stime); z=random(9999); /*计算机想一个随机数*/ printf("I have a number with 4 digits in mind,please guess.\n"); for(c=1;;c++) /*c: 猜数次数计数器*/ { printf("Enter a number with 4 digits:"); scanf("%d",&g); /*请人猜*/ a=z;j=0;k=0;l[0]=l[1]=l[2]=l[3]=0; for(i=1;i<5;i++) /*i:原数中的第i位数。个位为第一位,千位为第4位*/ { s=g;m=1; for(t=1;t<5;t++) /*人所猜想的数*/ { if(a%10==s%10) /*若第i位与人猜的第t位相同*/ { if(m&&t!=l[0]&&t!=l[1]&&t!=l[2]&&t!=l[3]) { j++;m=0;l[j-1]=t; /*若该位置上的数字尚未与其它数字“相同”*/ } /*记录相同数字时,该数字在所猜数字中的位置*/ if(i==t) k++; /*若位置也相同,则计数器k加1*/ } s/=10; } a/=10; } printf("You hane correctly guessed %d digits,\n",j); printf("and correctly guessed %d digits in exact position.\n",k); if(k==4) break; /*若位置全部正确,则人猜对了,退出*/ } printf("Now you have correctly guessed the whole number after %d times.\n",c); } Now you have correctly guessed the whole number after 7 times. *思考题 猜数游戏。由计算机“想”一个数请人猜,人输入猜的数,如果猜对了,则结束游戏,否则计算机会给出提示,指出人猜的数是太大,还是太小。当一个数猜了20次还未猜中时,应停止猜数者继续游戏的权力,从程序中退出。 92.人机猜数游戏(2) 将以上游戏(91.人机猜数游戏)双方倒一下,请人想一个四位的整数,计算机来猜,人给计算机提示信息,最终看计算机用几次猜出一个人“想”的数。请编程实现。 *问题分析与算法设计 解决这类问题时,计算机的思考过程不可能象人一样具完备的推理能力,关键在于要将推理和判断的过程变成一种机械的过程,找出相应的规则,否则计算机难以完成推理工作。 基于对问题的分析和理解,将问题进行简化,求解分为两个步聚来完成:首先确定四位数字的组成,然后再确定四位数字的排列顺序。可以列出如下规则: 1)分别显示四个1,四个2,……,四个0,确定四位数字的组成。 2)依次产生四位数字的全部排列(依次两两交换全部数字的位置)。 3)根据人输入的正确数字及正确位置的数目,进行分别处理: (注意此时不出现输入的情况,因为在四个数字已经确定的情况下,若有3个位置正确,则第四个数字的位置必然也是正确的) 若输入4:游戏结束。 判断本次输入与上次输入的差值 若差为2:说明前一次输入的一定为0,本次输入的为2,本次交换的两个数字的位置是正确的,只要交换另外两个没有交换过的数字即可结束游戏。 若差为-2:说明前一次输入的一定为2,本次的一定为0。说明刚交换过的两个数字的位置是错误的,只要将交换的两个数字位置还原,并交换另外两个没有交换过的数字即可结束游戏。 否则:若本次输入的正确位置数<=上次的正确位置数 则恢复上次四位数字的排列,控制转3) 否则:将本次输入的正确位置数作为“上次输入的正确位置数”,控制转3)。 *程序说明与注释 #include #include void bhdy(int s,int b); void prt(); int a[4],flag,count; int main() { int b1,b2,i,j,k=0,p,c; printf("Game guess your number in mind is # # # #.\n"); for(i=1;i<10&&k<4;i++) /*分别显示四个1~9确定四个数字的组成*/ { printf("No.%d:your number may be:%d%d%d%d\n",++count,i,i,i,i); printf("How many digits have bad correctly guessed:"); scanf("%d",&p); /*人输入包含几位数字*/ for(j=0;j a[k+j]=i; /*a[]:存放已确定数字的数组*/ k+=p; /*k:已确定的数字个数*/ } if(k<4) /*自动算出四位中包的个数*/ for(j=k;j<4;j++) a[j]=0; i=0; printf("No.%d:your number may be:%d%d%d%d\n",++count,a[0],a[1],a[2],a[3]); printf("How many are in exact positions:"); /*顺序显示四位数字*/ scanf("%d",&b1); /*人输入有几位位置是正确的*/ if(b1==4){prt();exit(0);} /*四位正确,打印结果。结束游戏*/ for(flag=1,j=0;j<3&&flag;j++) /*实现四个数字的两两(a[j],a[k]交换*/ for(k=j+1;k<4&&flag;k++) if(a[j]!=a[k]) { c=a[j];a[j]=a[k];a[k]=c; /*交换a[j],a[k]*/ printf("No.%d:Your number may be: %d%d%d%d\n",++count,a[0],a[1],a[2],a[3]); printf("How many are in exact positins:"); scanf("%d",&b2); /*输入有几个位置正确*/ if(b2==4){prt();flag=0;} /*若全部正确,结束游戏*/ else if(b2-b1==2)bhdy(j,k); /*若上次与本次的差为2,则交换两个元素即可结束*/ else if(b2-b1==-2) /*若上次与本次的差为-2,则说明已交换的(a[j],a[k])是错误的 将(a[j],a[k]还原后,只要交换另外两个元素即可结束游戏*/ { c=a[j];a[j]=a[k];a[k]=c; bhdy(j,k); } else if(b2<=b1) { c=a[j];a[j]=a[k];a[k]=c; /*恢复交换的两个数字*/ } else b1=b2; /*其它情况则将新输入的位置信息作为上次的位置保存*/ } if(flag) printf("You input error!\n"); /*交换结果仍没结果,只能是人输入的信息错误*/ } void prt() /*打印结果,结束游戏*/ { printf("Now your number must be %d%d%d%d.\n",a[0],a[1],a[2],a[3]); printf("Game Over\n"); } void bhdy(int s,int b) { int i,c=0,d[2]; for(i=0;i<4;i++) /*查找s和b以外的两个元素下标*/ if(i!=s&&i!=b) d[c++]=i; i=a[d[1>;a[d[1>=a[d[0>; a[d[0>=i; /*交换除a[s]和a以外的两个元素*/ prt(); /*打印结果,结束游戏*/ flag=0; } *运行示例 假设人想的四位数是:7215 Game Begin Now guess your number in mind is # # # #. No.1:your number may be:1111 *问题的进一步讨论 本程序具有逻辑结构清析、算法简单正确的优点,但在接受人的输入信息时缺少必要的出错保护功能,同时在进行第三步推理过程中没有保留每次猜出的数字位置信息及人输入的回答,这样对于每次人输入的信息就无法进行合法性检查,即无法检查人的输入信息是否自相矛盾;同晨也无法充分利用前面的结果。 这些缺陷是可以改进的,但最后一个问题改进难度较大,留给大家自己去完成。 *思考题 “一条龙游戏”。在一个3×3的棋盘上,甲乙双方进行对弃,双方在棋盘上轮流放入棋子,如果一方的棋子成一直线(横、竖或斜线),则该方赢。请编写该游戏程序实现人与机器的比赛。比赛结果有三种:输、赢或平。 在编程过程中请首先分析比赛中怎样才能获胜,找出第一步走在什么位置就最可能赢 93.汉诺塔 约19世纪末,在欧州的商店中出售一种智力玩具,在一块铜板上有三根杆,最左边的杆上自上而下、由小到大顺序串着由64个圆盘构成的塔。目的是将最左边杆上的盘全部移到右边的杆上,条件是一次只能移动一个盘,且不允许大盘放在小盘的上面。 *问题分析与算法设计 这是一个著名的问题,几乎所有的教材上都有这个问题。由于条件是一次只能移动一个盘,且不允许大盘放在小盘上面,所以64个盘的移动次数是: 18,446,744,073,709,551,615 这是一个天文数字,若每一微秒可能计算(并不输出)一次移动,那么也需要几乎一百万年。我们仅能找出问题的解决方法并解决较小N值时的汉诺塔,但很难用计算机解决64层的汉诺塔。 分析问题,找出移动盘子的正确算法。 首先考虑a杆下面的盘子而非杆上最上面的盘子,于是任务变成了: *将上面的63个盘子移到b杆上; *将a杆上剩下的盘子移到c杆上; *将b杆上的全部盘子移到c杆上。 将这个过程继续下去,就是要先完成移动63个盘子、62个盘子、61个盘子….的工作。 为了更清楚地描述算法,可以定义一个函数movedisc(n,a,b,c)。该函数的功能是:将N个盘子从A杆上借助C杆移动到B杆上。这样移动N个盘子的工作就可以按照以下过程进行: 1) movedisc(n-1,a,c,b); 2) 将一个盘子从a移动到b上; 3) movedisc(n-1,c,b,a); 重复以上过程,直到将全部的盘子移动到位时为止。 *程序说明与注释 #include void movedisc(unsigned n,char fromneedle,char toneedle,char usingneedle); int i=0; int main() { unsigned n; printf("please enter the number of disc:"); scanf("%d",&n); /*输入N值*/ printf("\tneedle:\ta\t b\t c\n"); movedisc(n,'a','c','b'); /*从A上借助B将N个盘子移动到C上*/ printf("\t Total: %d\n",i); } void movedisc(unsigned n,char fromneedle,char toneedle,char usingneedle) { if(n>0) { movedisc(n-1,fromneedle,usingneedle,toneedle); /*从fromneedle上借助toneedle将N-1个盘子移动到usingneedle上*/ ++i; switch(fromneedle) /*将fromneedle 上的一个盘子移到toneedle上*/ { case 'a': switch(toneedle) { case 'b': printf("\t[%d]:\t%2d………>%2d\n",i,n,n); break; case 'c': printf("\t[%d]:\t%2d……………>%2d\n",i,n,n); break; } break; case 'b': switch(toneedle) { case 'a': printf("\t[%d]:\t%2d<……………>%2d\n",i,n,n); break; case 'c': printf("\t[%d]:\t %2d……..>%2d\n",i,n,n); break; } break; case 'c': switch(toneedle) { case 'a': printf("\t[%d]:\t%2d<…………%2d\n",i,n,n); break; case 'b': printf("\t[%d]:\t%2d<……..%2d\n",i,n,n); break; } break; } movedisc(n-1,usingneedle,toneedle,fromneedle); /*从usingneedle上借助fromneedle将N-1个盘子移动到toneedle上*/ } } 94.兎子产子 从前有一对长寿兎子,它们每一个月生一对兎子,新生的小兎子两个月就长大了,在第二个月的月底开始生它们的下一代小兎子,这样一代一代生下去,求解兎子增长数量的数列。 *问题分析与算法设计 问题可以抽象成下列数学公式: Un=Un-1+Un-2 其中: n是项数(n>=3)。它就是著名的菲波那奇数列,该数列的前几为:1,1,2,3,5,8,13,21… 菲波那奇数列在程序中可以用多种方法进行处理。按照其通项递推公式利用最基本的循环控制就可以实现题目的要求。 *程序说明与注释 #include int main() { int n,i,un1,un2,un; for(n=2;n<3;) { printf("Please enter required number of generation:"); scanf("%d",&n); if(n<3) printf("\n Enter error!\n"); /*控制输入正确的N值*/ } un=un2=1; printf("The repid increase of rabbits in first %d generation is as felow:\n",n); printf("l\tl\t"); for(i=3;i<=n;i++) { un1=un2; un2=un; un=un1+un2; /*利用通项公式求解N项的值*/ printf(i%10?"%d\t":"%d\n",un); } printf("\n"); } *运行结果 Please enter required number of generation: 20 The repid increase of rabbits in first 20 generation is as felow: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765 95.将阿拉伯数字转换为罗马数字 将大于0小于1000的阿拉伯数字转换为罗马数字。阿拉伯数字与罗马数字的对应关系如下: 1 2 3 4 5 …… I II III IV V …… *问题分析与算法设计 题目中给出了阿拉伯数字与罗马数字的对应关系,题中的数字转换实际上就是查表翻译。即将整数的百、十、个位依次从整数中分解出来,查找表中相应的行后输出对应的字符。 *程序与程序设计 #include int main() { static char *a[][10]={"","I","II","III","IV","V","VI","VII","VIII","IX" "","X","XX","XXX","XL","L","LX","LXX","LXXX","XCC", "","C","CC","CCC","CD","D","DC","DCC","DCCC","CM" }; /*建立对照表*/ int n,t,i,m; printf("Please enter number:"); scanf("%d",&n); /*输入整数*/ printf("%d=",n); for(m=0,i=1000;m<3;m++,i/=10) { t=(n%i)/(i/10); /*从高位向低位依次取各位的数字*/ printf("%s",a[2-m][t]); /*通过对照表翻译输出*/ } printf("\n"); } *运行结果 1. Please enter number:863 863=DCCCLXIII 2. Please enter number: 256 256=CCLVI 3. Please enter number:355 355=CCCLV 4. Please enter number:522 522=DXXII 5. Please enter number:15 15=XV *思考题 输入正整数N,产生对应的英文数字符串并输出,例如: 1 ONE 2 TWO 3 THREE 10 TEN 11 ELEVEN 135 ONE HUNDRED THIRTY FIVE 96.选美比赛 在选美大奖赛的半决胜赛现场,有一批选手参加比赛,比赛的规则是最后得分越高,名次越低。当半决决赛结束时,要在现场按照选手的出场顺序宣布最后得分和最后名次,获得相同分数的选手具有相同的名次,名次连续编号,不用考虑同名次的选手人数。例如: 选手序号: 1,2,3,4,5,6,7 选手得分: 5,3,4,7,3,5,6 则输出名次为: 3,1,2,5,1,3,4 请编程帮助大奖赛组委会完成半决赛的评分和排名工作。 *问题分析与算法设计 问题用程序设计语言加以表达的话,即为:将数组A中的整数从小到大进行连续编号,要求不改变数组中元素的顺序,且相同的整数要具有相同的编号。 普通的排序方法均要改变数组元素原来的顺序,显然不能满足要求。为此,引入一个专门存放名次的数组,再采用通常的算法:在尚未排出名次的元素中找出最小值,并对具有相同值的元素进行处理,重复这一过程,直到全部元素排好为止。 *程序说明与注释 #include #define NUM 7 /*定义要处理的人数*/ int a[NUM+1]={0,5,3,4,7,3,5,6}; /*为简单直接定义选手的分数*/ int m[NUM+1],l[NUM+1]; /*m:已编名次的标记数组 l:记录同名次元素的下标*/ int main() { int i,smallest,num,k,j; num=1; /*名次*/ for(i=1;i<=NUM;i++) /*控制扫描整个数组,每次处理一个名次*/ if(m==0) /*若尚未进行名次处理(即找到第一个尚未处理的元素)*/ { smallest=a; /*取第一个未处理的元素作为当前的最小值*/ k=1; /*数组l的下标,同名次的人数*/ l[k]=i; /*记录分值为smallest的同名次元素的下标*/ for(j=i+1;j<=NUM;j++) /*从下一个元素开始对余下的元素进行处理*/ if(m[j]==0) /*若为尚未进行处理的元素*/ if(a[j] { smallest=a[j]; /*则重新设置当覵最小值*/ k=0; /*重新设置同名次人数*/ l[++k]=j; /*重新记录同名次元素下标*/ } else if(a[j]==smallest) /*若与当前最低分相同*/ l[++k]=j; /*记录同名次的元素下标*/ for(j=1;j<=k;j++) /*对同名次的元素进行名次处理*/ m[l[j>=num; num++; /*名次加1*/ i=0; /*控制重新开始,找下一个没排名次的元素*/ } printf("Player-No score Rank\n"); for(j=1;j<=NUM;j++) /*控制输出*/ printf(" %3d %4d %4d\n",j,a[j],m[j]); } *运行结果 Player-No Score Rank 1 5 3 2 3 1 3 4 2 5 7 5 5 3 1 3 5 3 7 6 4 *思考题 若将原题中的“名次连续编号,不用考虑同名次的选手人数”,改为”根据同名次的选手人数对选手的名次进行编号“,那么应该怎样修改程序。 97.满足特异条件的数列 输入m和n(20>=m>=n>0)求出满足以下方程的正整数数列 i1,i2,…,in,使得:i1+i1+…+in=m,且i1>=i2…>=in。例如: 当n=4, m=8时,将得到如下5 个数列: 5 1 1 1 4 2 1 1 3 3 1 1 3 2 2 1 2 2 2 2 *问题分析与算法设计 可将原题抽象为:将M分解为N个整数,且N个整数的和为M,i1>=i2>=…>=in。分解整数的方法很低多,由于题目中有"i1>=i2>=…..>=in,提示我们可先确定最右边in元素的值为1,然后按照条件使前一个元素的值一定大于等于当前元素的值,不断地向前推就可以解决问题。下面的程序允许用户选定M和N,输出满足条件的所有数列。 *程序说明与注释 #include #define NUM 10 /*允许分解的最大元素数量*/ int i[NUM]; /*记录分解出的数值的数组*/ int main() { int sum,n,total,k,flag,count=0; printf("Please enter requried terms(<=10):"); scanf("%d",&n); printf(" their sum:"); scanf("%d",&total); sum=0; /*当前从后向前k个元素的和*/ k=n; /*从后向前正在处理的元素下标*/ i[n]=1; /*将最后一个元素的值置为1作为初始值*/ printf("There are following possible series:\n"); while(1) { if(sum+i[k] if(k<=1) /*若正要处理的是第一个元素*/ {i[1]=total-sum;flag=1;} /*则计算第一个元素的并置标记*/ else{ sum+=i[k–]; i[k]=i[k+1]; /*置第k位的值后k-1*/ continue; /*继续向前处理其它元素*/ } else if(sum+i[k]>total||k!=1) /*若和已超过total或不是第一个元素*/ { sum-=i[++k]; flag=0;} /*k向后回退一个元素*/ else flag=1; /*sum+i[k]=total&&k=1 则设置flag标记*/ if(flag) { printf("[%d]:",++count); for(flag=1;flag<=n;++flag) printf("%d",i[flag]); printf("\n"); } if(++k>n) /*k向后回退一个元素后判断是否已退出最后一个元素*/ break; sum-=i[k]; i[k]++; /*试验下一个分解*/ } } *运行结果 Please enter requried terms(<=10):4 their sum:8 There are following possible series: [1]: 5111 [2]: 4211 [3]: 3311 [4]: 3221 [5]: 2222 |
|