劉汝佳《演算法競賽入門經典(第二版)》習題(二)
劉汝佳《演算法競賽入門經典(第二版)》第二章習題
習題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個非負整數a,b,c,表示每種隊形排尾的人數(a<3,b<5,c<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的餘數。可以發現70是5和7的最小公倍數,21是3和7的最小公倍數,15是3和5的最小公倍數,105是3、5、7的最小公倍數。因此這四句口訣的意思就是用任意兩數的最小公倍數乘第三個數並求和,對和求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時輸出如下:
#########
#######
#####
###
#
解析:這道題只需要找到倒三角形構造的規律即可。設行數為m(1≤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
解析:提示所說的陷阱從題目的n和m的範圍以及樣例輸入的第二組資料就能看得出來,普通的int型資料無法表示最大值達到106*106=1012級別的整數,所以要用到long long型整數。讓我感到疑惑的是,題中的輸入結束標誌是n=m=0,但從兩個正整數的範圍可以看出只有n可能為0,另外從表示式中又可以知道n也不能為0(分母不能為0)。按照n=m=0的條件,輸入n=0,m不為0,果然出了問題:
下面程式碼給出兩種結束標記(一種是題目所給,一種是n和m都不能為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 分數化小數
輸入正整數a,b,c,輸入a/b的小數形式,輸入包含多組資料,結束標記為a=b=c=0精確到小數點後c位。a,b≤106,c≤100。
解析:這題除了需要注意資料規模外難點在於保留的小數位數要手動輸入,我們需要自己寫程式模擬保留小數位數的過程(注意四捨五入),要直接通過計算機的浮點運算來實現不太可能(至少我試過了不行,有大神能實現的話請在評論區貼出程式碼,感激不盡),因為這涉及到多次型別轉換,而每次從高到低的型別轉換都會直接截斷導致資料丟失,所以只能按位輸出,在需要輸出的最後一位需要根據下一位的數值來判斷是否進位。
2018.9.30更新:直接用double強行求解可行,但模擬豎式求商的思想還是要掌握。
#include <iostream>
#include <iomanip>
using namespace std;
int main(){
double a,b,c;
int n=0;
while (cin >> a >> b >> c && a && c && c) {
cout << "Case " << ++n << ':' << setiosflags(ios::fixed) << setprecision(c) << a/b << endl;
}
return 0;
}
習題2-6 排列
用 1,2,3…,9組成3個三位數abc,def和ghi,每個數字恰好使用一次,要求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~9這9個數字不能每個只能出現一次,那麼輸出條件就是三個三位數拆分後的和要正好等於45(1+2+…+9=45)。
#include <cstdio>
int a[9] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
bool isOK (int val) {
for (int &i : a)
if (i == val) {
i = -1;
return true;
}
return false;
}
int main (void) {
int i,j,k;
for (i = 123; i <= 329; i++) {
j = i*2;
k = i*3;
if (isOK(i/100) && isOK(i/10%10) && (i%10) && isOK(j/100) && isOK(j/10%10) && isOK(j%10) && isOK(k/100) && isOK(k/10%10) && isOK(k%10))
printf ("%d %d %d\n",i,j,k);
for (int t = 1; t < 10; ++t)
a[t] = t;
}
return 0;
}