刘汝佳《算法竞赛入门经典(第二版)》习题(二)

时间:2023-02-26 23:04:32

刘汝佳《算法竞赛入门经典(第二版)》第二章习题

习题2-1 水仙花数

输出100~999中的所有水仙花数。若3位数ABC满足ABC=A²+B²+C²,则称其为水仙花数。例如:153=1²+5²+3²,所以153是水仙花数。

解析:只有1000个数,直接暴搜就好了。

#include <cstdio>
int main (void)
{
int first = 1;
for (int i = 100; i < 1000; i++)
{
int c = i%10;
int b = i/10%10;
int a = i/100;
if (a*a*a+b*b*b+c*c*c == i)
{
if (first)
first = 0;
else
printf (" ");
printf ("%d",i);
}
}
return 0;
}

习题2-2 韩信点兵

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

样例输入:

2 1 6

2 1 3

样例输出:

Case 1: 41

Case 2:No answer

解析:文件结束标志为EOF,在Windows下为Ctrl+Z+Enter,在Unix/Linux/mac OS(其实后两者都是类Unix)下为Ctrl+D

因为数不大,可以用最简单的暴搜解决,但更巧妙的方法是用最小公倍数,韩信点兵的问题在明朝就出现了,明朝数学家程大位在他所著的《算法统宗》中就暗示了此题解法:

三人同行七十稀,

五数梅花甘一枝,

七子团圆正半月,

除百零五便得知。

甘一是21,正半月是15,除百零五的意思就是求105的余数。可以发现7057的最小公倍数,2137的最小公倍数,1535的最小公倍数,105357的最小公倍数。因此这四句口诀的意思就是用任意两数的最小公倍数乘第三个数并求和,对和求105的余数即可得到答案。

解法一:

#include <cstdio>
int main (void)
{
int a,b,c,kase = 0;
while (scanf ("%d%d%d",&a,&b,&c) != EOF)
{
int i;
for (i = 10; i <= 100; i++)
{
if (i%3 == a && i%5 == b && i%7 ==c)
{
printf ("Case %d: %d\n",++kase,i);
break;
}
}
if (i > 100)
printf ("No answer\n");
}
return 0;
}
解法二:

#include <cstdio>
int main (void)
{
int a,b,c,kase = 0;
while (scanf ("%d%d%d",&a,&b,&c) != EOF)
{
int sum;
sum = 70*a+21*b+15*c;
if (sum%105 <= 100)
printf ("Case %d: %d\n",++kase,sum%105);
else
printf ("No answer\n");
}
return 0;
}

习题2-3 倒三角形

输入正整数n≤20,输入一个n层的倒三角形。例如,n=5时输出如下:

#########

#######

  #####

  ###

    #

解析:这道题只需要找到倒三角形构造的规律即可。设行数为m1≤m≤n),每行需要打印#号和空格,第m行的空格数量为 m-1‘#’数量为2(n-m)-1(先找出正三角形的行数与打印符号数的规律再想办法把行数对调即可)。

#include <cstdio>
int main (void)
{
int i,j,n;
scanf ("%d",&n);
if (n <= 20)
{
for (i = 0; i < n; i++)
{
for (j = 0; j < i; j++)
printf (" ");
for (j = 0; j < 2*(n-i)-1; j++)
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

解析:提示所说的陷阱从题目的nm的范围以及样例输入的第二组数据就能看得出来,普通的int型数据无法表示最大值达到106级别的整数,所以要用到long long型整数。让我感到疑惑的是,题中的输入结束标志是n=m=0,但从两个正整数的范围可以看出只有n可能为0,另外从表达式中又可以知道n也不能为0(分母不能为0)。按照n=m=0的条件,输入n=0m不为0,果然出了问题:

刘汝佳《算法竞赛入门经典(第二版)》习题(二)

下面代码给出两种结束标记(一种是题目所给,一种是nm都不能为0):

#include <cstdio>
int main (void)
{
long long n,m;
int kase = 0;
double sum;
while (scanf ("%lld%lld",&n,&m) == 2 && !(n == 0 && m == 0))//如果是n和m都不能为0,将&&后面的判断改为(n != 0 && m != 0)即可。
{
sum = 0.0;
for (long long i = n; i <= m; i++)
sum += 1.0/(double)(i*i);
printf ("Case %d: %.5f",++kase,sum);
}
return 0;
}

习题2-5 分数化小数

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

解析:这题除了需要注意数据规模外难点在于保留的小数位数要手动输入,我们需要自己写程序模拟保留小数位数的过程(注意四舍五入),要直接通过计算机的浮点运算来实现不太可能(至少我试过了不行,有大神能实现的话请在评论区贴出代码,感激不尽),因为这涉及到多次类型转换,而每次从高到低的类型转换都会直接截断导致数据丢失,所以只能按位输出,在需要输出的最后一位需要根据下一位的数值来判断是否进位。


#include <cstdio>
int main (void)
{
long long a,b;
int c;
while (scanf ("%lld%lld%d",&a,&b,&c) == 3 && !(a == 0 && b == 0 && c == 0))
{
printf ("%lld.",a/b);//先输出整数部分和小数点
a %= b;
for (int i = 1; i < c; i++)
{
printf ("%lld",a*10/b);
a = a*10%b;
}
if (a*10%b*10/b < 5)//对需要输出的最后一位进行是否进位的判断
printf ("%lld",a*10/b);
else
printf ("%lld",a*10/b+1);
}
return 0;
}

习题2-6 排列

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

解析:因为搜索范围是100~999而已,所以提示的意思大概就是让我们直接用暴搜找出符合条件的数就好了。即使是暴搜我们还是可以将范围再缩小一点,根据题目要求,可以用到的最小的数(作为abc)是123,最大的数(作为ghi)是987,由最大数可以根据3个数的比例推算出最小数abc的上限为987÷3=329,因此最小数abc的范围是123~329(如果不缩小范围还需要加一个判断ghi是否大于1000的判断条件,稍加思考即可发现超过329的三位数abc绝大部分对应的三位数ghi都超过了1000,不缩小范围浪费了很多时间)。题目要求1~99个数字不能每个只能出现一次,那么输出条件就是三个三位数拆分后的和要正好等于451+2+…+9=45)。


#include <cstdio>
int main (void)
{
int i,j,k;
for (i = 123; i <= 329; i++)
{
j = i*2;
k = i*3;
if (i/100+i/10%10+i%10+j/100+j/10%10+j%10+k/100+k/10%10+k%10 == 45)
printf ("%d %d %d\n",i,j,k);
}
return 0;
}