《算法竞赛入门经典(第二版)》习题解析——第二章

习题2-1

题目:
输出100-999中的所有水仙花数。若3位数ABC满足ABC=A3+B3+C3,则称其为水仙花数。例如153=13+53+33,所以153是水仙花数。

解析:

采用暴力搜索的思想,穷尽所有可能的解。

代码

#include<stdio.h>
#include<math.h>
int main()
{
	for(int i=1;i<10;i++)
	{
		for(int j=0;j<10;j++)
		{
			for(int k=0;k<10;k++)
			{
				int s;
				s=i*100+j*10+k;
				if(s==pow(i,3)+pow(j,3)+pow(k,3))
				printf("%d\n",s);
				
			}
		} 
	}
	return 0;
}

习题2-2

题目:
相传韩信才智过人,从不直接清点自己军队的人数,只要让士兵先后以三人一排、五人一排、七人一排地变换队形,而他每次只掠一眼队伍的排尾就知道总人数了。输入包含多组数据,每组数据包含3个非负整数a,b,c,表示每种队形排尾的人数(a<3,b<5,c<7),输出总人数的最小值(或报告无解)。已知总人数不小于10,不超过100。输入到文件结束为止。

样例输入:
2 1 6
2 1 3
样例输出:
case 1: 41
case 2: No answer

解析1:

分析题意,即表示求解最小总人数s(10≤s≤100),分别对3、5、7取余为a,b,c,实际上也是一种暴力搜索。关于文件结束,在参考不同的答案时有人根据其标志为EOF,所以while的条件为while (scanf ("%d%d%d",&a,&b,&c) != EOF),但此题中我用了重定向的方法,故循环条件为while(scanf("%d%d%d",&a,&b,&c)==3)。

代码如下:

#include<stdio.h>
int main()
{   int a,b,c,i,n=0;
    freopen("2-2 datain.txt","r",stdin);//需要提前在cpp文件同一目录下建立txt文件作为scanf的读取路径
    freopen("2-2 dataout.txt","w",stdout);
    while(scanf("%d%d%d",&a,&b,&c)==3)
	{   
		++n;
		for(i=10;i<=100;i++)
	   {
			if(i%3==a && i%5==b && i%7==c)
			{
				printf("Case %d: %d\n",n,i);
				break;
			}
						
	   }
	   if(i>100) printf("No answer\n");	   		
	}
	
	return 0;
}

解析2

此题更巧妙的方法是用最小公倍数,明朝数学家程大位在他所著的《算法统宗》中就暗示了此题解法:
三人同行七十稀,
五数梅花甘一枝,
七子团圆正半月,
除百零五便得知。
甘一是21,正半月是15,除百零五的意思就是求105的余数。可以发现35是5和7的最小公倍数,21是3和7的最小公倍数,15是3和5的最小公倍数,105是3、5、7的最小公倍数。因此这四句口诀的意思就是用任意两数的最小公倍数乘第三个数后三项进行求和,对和求105的余数即可得到答案。

代码如下:

#include <stdio.h>
int main ()
{
    int a,b,c,i,n=0;
    freopen("2-2 datain.txt","r",stdin);
    freopen("2-2 dataout.txt","w",stdout);
    while (scanf ("%d%d%d",&a,&b,&c)==3)
    {
        ++n;
        for (i = 10; i <= 100; i++)
        {
            if (i%3 == a && i%5 == b && i%7 ==c)
            {
                printf ("Case %d: %d\n",++n,i);
                break;
            }
        }
        if (i > 100)
            printf ("No answer\n");
    }
    return 0;
}

习题2-3

题目:
输入正整数n≤20,输出一个n层的倒三角形。

解析:

找出三角形的规律即可。第i(i从1开始,从上往下数)行有(2i-1)个“ * ”,和(n-i)个空格,利用循环便可完成此题。

代码:

#include<stdio.h>
int main()
{
	int n;
	scanf("%d",&n);
	if(n<=20)
	{
		for(int i=n;i>=1;i--)
		{		
			for(int j=0;j<n-i;j++)
				printf(" ");
			for(int k=0;k<2*i-1;k++)
				printf("*"); 
			printf("\n");
		    				
		}
	}
	return 0; 
}

习题2-4

题目:
输入两个正整数n<m<106,输出1/n2+1/(n+1)2+…+1/m2,保留5位小数。输入包含多组数据,结束标记为n=m=0。提示:本题有陷阱。

样例输入:
2 4
65536 655360
0 0
样例输出:
case 1: 0.42361
case 2: 0.00001

解析:

从题意可知,本题的核心思想仍然为循环,但注意题目所说“有陷阱”,在可以看出陷阱主要体现在数据的类型和范围中,下面我们根据题意第一次编写代码。

代码(有bug版):

#include<stdio.h>
int main()
{
	int n,m,t,kase=0;
	double s; 
	while(scanf("%d%d",&n,&m)==2&&n!=0&&m!=0)
	{
		if(n>m) {t=n;n=m;m=t;}
		s=0;
		for(int i=n;i<=m;i++)
			s+=1.0/(i*i);	
		printf("Case %d: %.5lf",++kase,s);
	} 
	return 0;	
}

我们来看下运行结果:
在这里插入图片描述
我们可以看出,第一行数据输出结果和预期一致,由此判断程序基本逻辑没有问题。但是,在第二个数据出现了“#INFO”的字样,这是由于当m=655360时,普通的int型数据无法表示最大值达到106 * 106=1012级别的整数,产生溢出问题,使得i * i值在截断为int时结果为0而出现“被0除”的错误,一种方法可将n,m,i的类型直接定义为long long型,另一种更为巧妙的方法是将s+=1.0/(i*i);改为s+=1.0/i/i;

习题2-5

题目:
输入正整数a,b,c,输入a/b的小数形式,精确到小数点后c位。a,b≤106,c≤100。,输入包含多组数据,结束标记为a=b=c=0

样例输入:
1 6 4
0 0 0
样例输出:
case 1: 0.1667

解析:

本题的关键即为输出的小数位数需要程序来控制,我们需要自己写程序模拟保留小数位数的过程(注意四舍五入),可以根据我们进行除法运算的过程:拿样例1/6举例,先求出他们的商,既1÷6=0······1。商为0余数为1。那么此时就打印出”0.”来。此时应该算十分位,所以余数1应该乘以10为10,10÷6=1······4。商为1余数为1。此时打印出来“1”来。则输出的为“0.1”。再算百分位:40÷6=6······4,商为6余数为4。此时打印出“6”。输出界面输出“0.16”。以上办法得出千分位和万分位。输出界面输出结果为“0.1666”。此时就有四位小数,但是还要检查最后一位是否为四舍五入。再次计算十万分位,发现商为6,那么万分位就要进一。最后结果为“0.1667”。

代码:

#include<stdio.h>
int main()
{
	int a,b,c,z,n=0;
	while(scanf("%d%d%d",&a,&b,&c)==3&&a!=0&&b!=0&&c!=0)
	{
		z=a/b;a=a%b;
		int s[c];
		for(int i=1;i<c;i++)
		{
		    s[i]=(a*10)/b;
			a=(a*10)%b;		
		}
		if(((a*10)%b)*10/b<5)//当第c+1位小于5时,正常输出结果(四舍) 
		{
			s[c]=((a*10)/b)%10;
			printf("Case %d:%d.",++n,z);
			for(int i=1;i<=c;i++)
				printf("%d",s[i]);		
		}
		else if((a*10)/b!=9)//当c+1位大于等于5且第c位不为9时,输出时第c位的结果需要加一 (五入) 
		{
			s[c]=((a*10)/b+1)%10;
			printf("Case %d:%d.",++n,z);
			for(int i=1;i<=c;i++)
				printf("%d",s[i]);	
			
		}
		else if((a*10)/b==9)//当c+1位大于等于5且第c位为9时,需要依次向前进位。 
		{
		   s[c]=(a*10)/b;
		   for(int j=c;j>0;j--)
		   {
		   	if(s[j]==9) 
			   {
			   	s[j]=(s[j]+1)%10;
			    s[j-1]=(s[j-1]+1)%10;
			   }
		   	else 
			   break;
		   }
		   if(s[1]==0) z++; 
		   printf("Case %d:%d.",++n,z);
		   for(int i=1;i<=c;i++)
				printf("%d",s[i]);							
		} 	
	}	
	return 0;
}

说明:代码逻辑基本没有问题,但不够简洁,能力有限暂且没有写出更好的方法,不足之处还请指教!

习题 2-6

题目:
用 1,2,3…,9组成3个三位数abc,defghi,每个数字恰好使用一次,要求abc:def:ghi=1:2:3。按照“abc def ghi”的格式输出所有解,每行一个解。提示:不必太动脑筋。

解析:

提示的意思就是让我们直接用暴搜找出符合条件的数就好了。根据题目要求,可以用到的最小的数(作为abc)是123,最大的数(作为ghi)是987,由最大数可以根据3个数的比例推算出最小数abc的上限为987÷3=329,因此最小数abc的范围是123~329。
求解关键: 九个数加起来是45,相乘是362880,这两个数是不变的,根据这两个条件求解。

代码(简单版):

#include<stdio.h>
int main()
{
	for (int i = 123; i <=987/3 ; i++) {
    	if (i%10+i/10%10+i/10/10%10+(2*i)%10+(2*i)/10%10+(2*i)/10/10%10+(3*i)%10+(3*i)/10%10+(3*i)/10/10%10==45&&(i%10)*(i/10%10)*(i/10/10%10)*((2*i)%10)*((2*i)/10%10)*((2*i)/10/10%10)*((3*i)%10)*((3*i)/10%10)*((3*i)/10/10%10)==362880){
            printf("%d %d %d\n",i,2*i,3*i);}   
   }
}

代码(高级版):

#include <stdio.h>
#include <stdlib.h>

void result(int num, int *result_add, int *result_mul)
{
    int i, j, k;

    i = num / 100;        //百位
    j = num / 10 % 10;    //十位
    k = num % 10;         //个位

    *result_add += i + j + k;    //分解出来的位数相加
    *result_mul *= i * j * k;    //相乘
}

int main()
{
    int i, j, k;
    int result_add, result_mul;
    for(i = 123; i <=333; i++)
    {
        j = i * 2;
        k = i * 3;
        result_add = 0;
        result_mul = 1;
        result(i,&result_add,&result_mul);
        result(j,&result_add,&result_mul);
        result(k,&result_add,&result_mul);
        if(result_add == 45 && result_mul == 362880)
            printf("%d %d %d\n", i, j, k);
    }
    return 0;

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值