【Uva 題庫解題】C++ 個人解題筆記(一顆星選集) - part 1 === [TOC] 一篇十題讓你看到爽! CPE 49 道必考題,資料來源:https://cpe.mcu.edu.tw/environment.php ## 1. Vito’sfamily PDF Source:https://onlinejudge.org/external/100/10041.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=a737 題目翻譯: 世界知名的黑幫 Vito Deadstone 要搬到紐約了。他在那邊有個大家族,他們全部都住在 Lamafia 大街上。因為他時常要拜訪他所有的親戚,所以他想要找一間離他們很近的房子。 Vito 想最小化與他們的距離和,然後還威脅你要寫個程式幫他解決這個問題。 精選單字: - avenue (n.) 大街、大道;方法、途徑、管道 - relative (n.) 親戚、親屬 - (adj.) 比較的;相對的 解題思路: 首先要搞懂這個距離的定義。題目給定多個門牌號碼 $s_1, s_2, ..., s_r$,選定一個數 $x$ 作為 Vito 的家,因此總距離為: $$\sum^r_{i - 1} |x - s_i|$$ 這題的重點是 $x$ 要取什麼,直接說結論好了,就是取中位數。 為什麼是中位數?這就需要會一點微分的概念去做數學證明了。 但是本系列僅止於解題,這部分不多贅述,反倒是有個方法能夠直覺地看出為什麼是中位數: 假設其中一個輸入測資是 `[1, 2, 4, 8, 10]`,此時我們將這些數字一個個代入上面那個公式,即可列表: | x 值 | 總距離 | |------|------------------------------| | 1 | 0+1+3+7+9 = 20 | | 2 | 1+0+2+6+8 = 17 | | 4 | 3+2+0+4+6 = 15 | | 8 | 7+6+4+0+2 = 19 | | 10 | 9+8+6+2+0 = 25 | 唉呦,可以發現在中位數的地方就是最小值 15。 如果你不信的話,可以拿範例測資來試試看: `2 4` | x 值 | 總距離 | | -------- | -------- | | 2 | 0 + 2 = 2 | | 4 | 2 + 0 = 2 | `2 4 6` | x 值 | 總距離 | | -------- | -------- | | 2 | 0 + 2 + 4 = 6 | | 4 | 2 + 0 + 2 = 4 | | 6 | 4 + 2 + 0 = 6 | `1 2 5 9999` | x 值 | 總距離 | | -------- | -------- | | 1 | 0 + 1 + 4 + 9998 = 10003 | | 2 | 1 + 0 + 3 + 9997 = 10001 | | 5 | 4 + 3 + 0 + 9994 = 10001 | | 9999 | 9998 + 9997 + 9994 = 29989 | 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ int T; cin >> T; while (T--){ int r; cin >> r; vector <int> s(r); for (int i = 0; i < r; ++i){ cin >> s[i]; } sort(s.begin(), s.end()); int median = s[r/2]; int result = 0; for (int i = 0; i < r; ++i){ result += abs(median - s[i]); } cout << result << '\n'; } return 0; } ``` ## 2. Hashmat the Brave Warrior PDF Source:https://onlinejudge.org/external/100/10055.pdf zeroJudge:https://zerojudge.tw/ShowProblem?problemid=a012 題目翻譯: Hashmat 是個勇敢的戰士,帶領著他的一群年輕士兵,從某地移動到另一個地方與敵人對抗。打仗之前他會計算一件事,就是他的士兵數量與敵對士兵數量的差距。這個差距會讓他決定要不要打。Hashmat 的士兵數量都不會大於敵對士兵。 沒有解題思路,so ez。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ long long h, o; while (cin >> h >> o){ cout << abs(h - o) << '\n'; } return 0; } ``` ## 3. Primary Arithmetic PDF Source:https://onlinejudge.org/external/100/10035.pdf ZeroJudge:https://zerojudge.tw/ShowProblem?problemid=c014 題目翻譯: 孩子們被教導從右到左逐位相加多位數。許多人發現「進位」操作——即從一位數位置進位 1 到下一位數位置相加——是一項顯著的挑戰。你的任務是計算每組加法問題中的進位操作次數,以便教育工作者能夠評估其難度。 精選單字: - assess (v.) 評價;對...估價 解題思路: 唯一要注意的就是記得設 carry 變數表示進位(1 或 0),在每次的 sum 中都要加上 carry,不然的話就會判斷錯誤,然後 WA。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ long long n1, n2; while (cin >> n1 >> n2 && (n1 || n2)){ int carry = 0, operation = 0; while (n1 > 0 || n2 > 0){ int digit_1 = n1 % 10, digit_2 = n2 % 10; int sum = digit_1 + digit_2 + carry; sum >= 10 ? carry = 1, operation++ : carry = 0; n1 /= 10, n2 /= 10; } if (operation){ cout << operation << (operation > 1 ? " carry operations." : " carry operation."); } else{ cout << "No carry operation."; } cout << '\n'; } return 0; } ``` ## 4. The 3n + 1 problem PDF Source:https://onlinejudge.org/external/1/100.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=c039 題目翻譯: 電腦科學中的問題通常被分類為某類問題(如 NP、不可解、遞迴)。在本題中,你將分析一個演算法的性質,其分類對於所有可能的輸入尚不清楚。 考慮以下演算法: 1. 輸入 $n$ 2. 輸出 $n$ 3. 如果 $n = 1$ 則停止 4. 如果 $n$ 是奇數則 $n$ ← $3n+1$ 5. 否則 $n$ ← $n/2$ 6. 轉到步驟 2 給定輸入 `22`,會列印出以下數列: `22 11 34 17 52 26 13 40 20 10 5 16 8 4 2 1` 據推測,上述演算法對於任何整數輸入值,均會終止(當列印出 1 時)。儘管該演算法簡單,但是否屬實尚不清楚。已驗證,對於所有整數 $n$ 滿足 $0 < n < 1,000,000$ (實際上遠超此範圍的更多數值),該推測成立。 給定一個輸入 $n$ ,可以確定在列印出 1 前後(包括 1)的列印數量。對於給定的 $n$ ,這被稱為 $n$ 的循環長度。在上述範例中,22 的循環長度為 16。 對於任意兩個數值 $i$ 和 $j$ ,您需要確定所有數值(包括 $i$ 和 $j$ )之間的最大循環長度。 精選單字: - conjecture (n.)(v.) 推測、猜測。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int getCycleLength(int n){ int count = 1; while (n != 1){ if (n % 2 == 1) n = 3 * n + 1; else n /= 2; count++; } return count; } int main(){ int i, j; while (cin >> i >> j){ int from = min(i, j); int to = max(i, j); int max_cycle = 0; for (int i = from; i <= to; ++i){ int cycle_length = getCycleLength(i); if (cycle_length > max_cycle) max_cycle = cycle_length; } cout << i << " " << j << " " << max_cycle << '\n'; } return 0; } ``` 唯一要注意的就是 for 迴圈的條件,寫成 `<=` 而非 `<`,因為題目說包含 i, j,所以是閉區間 [i, j]。 其他就是按照題目演算法流程去走,不要看題目寫一大堆廢話。 ## 5. You can say 11 PDF Source:https://onlinejudge.org/external/109/10929.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=d235 沒啥好翻譯的,就是給你一個數字,這數字有可能到 1000 位數,然後求數字 % 11 是否等於 0。 解題思路: 先說這是 Python 天堂,我也很想用 Python... QQ,但為了磨練 CPP 實力只好動腦一下惹~ 在 C++ 中要處理這種位數大的數字,就是用字串去存他。 然後 11 倍數判別法相信各位都還知道,就是奇數位相加 - 偶數位相加 = 0 的時候就是 11 的倍數。 如 121 是 11 的平方,1 + 1 - 2 = 0,因此這是 11 的倍數。 所以在 C++ 程式碼中要做的就是取出偶數位跟奇數位、分別求他們的和然後相減,看是不是 0。 這樣寫好像比較麻煩,所以可以換個思路,每個偶數位跟奇數位一加一減也可以做到判斷 11 倍數的事情。 範例程式碼: ```cpp= #include <iostream> #include <string> using namespace std; bool isMultipleOf11(const string& num){ int sum = 0; for (size_t i = 0; i < num.size(); ++i){ int digit = num[i] - '0'; // 把字串轉整數,用 stoi() 也可以 if (i % 2 == 0) sum += digit; else sum -= digit; } return sum % 11 == 0; } int main(){ string num; while (cin >> num && num != "0"){ cout << num << ( isMultipleOf11(num) ? " is a multiple of 11." : " is not a multiple of 11."); cout << '\n'; } return 0; } ``` ## 6. Bangla Numbers PDF Source:https://onlinejudge.org/external/101/10101.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=a741 題目翻譯: 孟加拉數字一般都用 'kuti' (10000000), 'lakh' (100000), 'hajar' (1000), 'shata' (100) 來展開並轉換為文字。你要撰寫一個程式,將給定的數字轉換為包含這些單位的文字。 輸入說明: 輸入檔案可能包含多個測試案例。每個案例將包含一個非負數,且不大於 999999999999999。 輸出說明: 對於每個輸入案例,你必須輸出一行,以四位數調整的案例編號開始,後接轉換後的文字。 解題思路: 可以發現題目的 9 有 15 位數,而 long long 可以處理到 19 位數,所以可以直接用 long long 做。 另外可以從輸入測資 2 `45897458973958` 發現,輸出結果是 `45 lakh 89 hajar 7 shata 45 kuti 89 lakh 73 hajar 9 shata 58`。 這個有點像程式溢位(overflow)的概念,但他是單位用完後,回到最小單位,繼續往上表示有多少個 `45 kuti 89 lakh 73 hajar 9 shata 58`,如上範例測資就是 `45 lakh 89 hajar 7 shata` 個的 `45 kuti 89 lakh 73 hajar 9 shata 58`。 那這部份我們可以拆解成兩個部份來做,分別是「高位數的 kuti 部分」跟「低位數的剩餘部分」。 高位數的 kuti 部分就是 `45897458973958 / 10000000` 後的結果,也就是 `45 kuti 89 lakh 73 hajar 9 shata 58` 。 低位數就是 `45897458973958 % 10000000` 後的結果,表示 kuti 前面需要用較小的單位去表示。 高位數就用遞迴優先處理,低位數的最後再處理,於是請看範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; void printBangla(long long n) { if (n >= 10000000) { printBangla(n / 10000000); cout << " kuti"; n %= 10000000; } if (n >= 100000) { printBangla(n / 100000); cout << " lakh"; n %= 100000; } if (n >= 1000) { printBangla(n / 1000); cout << " hajar"; n %= 1000; } if (n >= 100) { printBangla(n / 100); cout << " shata"; n %= 100; } if (n > 0) { cout << " " << n; } } int main() { ios::sync_with_stdio(false), cin.tie(nullptr); long long num; int caseNum = 1; while (cin >> num) { cout << setw(4) << caseNum++ << "."; if (num == 0) { cout << " 0"; } else { printBangla(num); } cout << '\n'; } return 0; } ``` ## 7. List of Conquests PDF Source:https://onlinejudge.org/external/104/10420.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=a743 題目翻譯: 在第一幕中,Leporello 向 Donna Elvira 述說著有關他主人長長的征服名單: 「這是我主人所愛的美女名單,是我親自整理的:來看吧,跟我一起讀。在義大利(Italy)有 640 位,在德國(Germany)有 231 位,100 位在法國(France),91 位在土耳其(Turkey);但在西班牙(Spain)已有 1003 位!其中有鄉村女孩、女僕、城市美人;還有伯爵夫人、男爵夫人、侯爵夫人、公主:各個階層、各個身材、各個年齡的女性。」 (Madamina, il catalogo è questo) 由於 Leporello 按時間順序紀錄了 Don Giovanni 「愛過」的「美女」,他每次向他人展示主人的征服成果時都感到非常麻煩,因為他需要按國籍逐一計算「美女」的數量。你需要幫助 Leporello 進行計數。 輸入說明: 輸入最多包含 2000 行,但首行除外。首行包含一個數字 n,表示接下來還有 n 行。每行最多 75 個字元,包含一個國家名稱(首個單詞)和 Giovanni 所愛的女性的名字(該行其餘單詞)。你可以假設所有國家名稱僅由一個單字組成。 輸出說明: 輸出包含按字母順序排列的行。每行以國家名稱開頭,後跟 Giovanni 在該國愛過的女性總數,兩者之間以空格分隔。 精選單字: - conquest (n.) 征服;性玩物 - maid (n.) (飯店的)女服務員;(家中的)女傭,女僕,侍女 - 女孩,少女,(未婚的)年輕女子 - countess (n.) 女伯爵;伯爵夫人 - baroness (n.) 女男爵;男爵夫人 - marchioness (n.) (英國)侯爵夫人;女侯爵 - chronological (adj.) 按事件發生的年代順序排列的 - troublesome (adj.) 令人煩惱的;討厭的;麻煩的 - nationality (n.) 國籍;民族 解題思路: 首先看到國家映射數字,就知道要用 map 或 unordered_map,因為題目要求輸出用字母順序,所以用 map 會比較方便。 有個蠻取巧的方法,就是可以透過 cin 只讀前面的字元,然後到空格就不讀了嘛對不對,之後再用 getline() 把後面的都吃掉就好。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main() { ios::sync_with_stdio(false), cin.tie(nullptr); int n; cin >> n; map<string, int> countries; for (int i = 0; i < n; ++i) { string country; cin >> country; string line; getline(cin, line); countries[country]++; } for (const auto& [x, y] : countries){ cout << x << " " << y << '\n'; } return 0; } ``` ## 8. What's Cryptanalysis? PDF Source:https://onlinejudge.org/external/100/10008.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=c044 密碼分析(Cryptanalysis)是破解他人密文的過程。這有時涉及對一段(加密)文字進行某種統計分析。你的任務是撰寫一個程式,對給定的文字進行簡單的分析。 精選單字: - cryptanalysis (n.) 密碼分析、密碼破譯 - else's 別人的、他人的 - encrypt (v.) 將…譯成密碼;把…編碼;把…加密 解題思路: 建 counts 整數陣列,表示字母出現的次數。 題目範例測資有非字母字元,所以要判斷是不是字母 `isalpha(ch)`。 後續處理字母時,先將所有字母轉成大寫或小寫(`tolower()` or `toupper()`)用 ASCII 碼特性去求出 A-Z 的位置(數字):`counts[ch - 'A']++;`。 'A' 根據你填了什麼函數去做調整。 之後建一個 `vector<pair<char, int>> freq` 用來存字母與出現頻率的映射表。 為什麼不用 map?因為題目要求出現頻率由大到小的排序,而 map 只能對 key 做排序,身為 value 的 int 就不行。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ int n; cin >> n; cin.ignore(); int counts[26] = {0}; for (int i = 0; i < n; ++i){ string line; getline(cin, line); for (char& ch : line){ if (isalpha(ch)){ ch = toupper(ch); counts[ch - 'A']++; } } } vector <pair<char, int>> freq; for (int i = 0; i < 26; ++i){ if (counts[i] > 0){ freq.push_back({char('A' + i), counts[i]}); } } sort(freq.begin(), freq.end(), [](const pair<char, int>& a, const pair<char, int>& b){ if (a.second != b.second) return a.second > b.second; else return a.first < b.first; }); for (auto [x, y] : freq){ cout << x << " " << y << '\n'; } return 0; } ``` ## 9. Decode the Mad man PDF Source:https://onlinejudge.org/external/102/10222.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=e578 題目翻譯: 曾經在BUET,有一位老教授完全瘋了。他開始用一些奇怪的詞語說話。沒有人能聽懂他的演講跟課堂內容。最後,BUET 當局陷入了極大的困境。已經沒有辦法讓那個人繼續留在在大學裡了。突然有一位學生(他肯定是 UVA ACM 章節的註冊作者,並且在 24-hours Online Judge 中有很好的排名)建立了一個能夠解碼那位教授說的話的程式。他的發明出現之後,大家再次感到安心,那位老教授也恢復了日常的教學工作。 所以,如果你有一天參觀 BUET,看到一位老師對著麥克風講話,而麥克風連接著一台配有語音識別軟體的IBM 電腦,學生們正從電腦螢幕上聽課,請不要驚訝!因為現在你的任務就是寫出同樣能解碼那位瘋狂老師語音的程式! 精選單字: - peculiar (adj.) 奇怪的;古怪的;獨特的 - thunder (n.) 雷(聲) - (v.) 打雷、怒吼 - 在此作為「驚嚇、嚇到」,我想應該是比喻打雷的那種聲響會讓人嚇到那樣。 解題思路: 我們正常用的鍵盤佈局如下: ``` ` 1 2 3 4 5 6 7 8 9 0 - = q w e r t y u i o p [ ] \ a s d f g h j k l ; ' z x c v b n m , . / ``` 如果是 k,則往左移兩位得到 h,剩下以此類推。 1. 建立一個 keyboard 字串,就是鍵盤上所有字元,依序排列(空格跟換行不在裡面)。 2. 輸入的每個字元,根據在這個字串裡面往左移兩位,取出來。 3. 空白和換行直接輸出。 字元處理部分,可用 string.find() 去找這個字元在 keyboard 字串的位置,然後位置 - 2 輸出。 範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ ios::sync_with_stdio(false), cin.tie(nullptr); string keyboard = "`1234567890-=qwertyuiop[]\\asdfghjkl;'zxcvbnm,./"; string line; while (getline(cin, line)){ for (const char& ch : line){ if (ch == ' ' || ch == '\n'){ cout << ch; } else{ size_t pos = keyboard.find(ch); // string::npos 表示 find() 找不到 // 這個其實不用加,因為測資保證有一個或多個單字 if (pos != string::npos && pos >= 2){ cout << keyboard[pos - 2]; } } } cout << '\n'; } return 0; } ``` ## 10. Summing Digits PDF Source:https://onlinejudge.org/external/113/11332.pdf zerojudge:https://zerojudge.tw/ShowProblem?problemid=c813 題目翻譯: 對於所有正整數 n,令 $f(n)$ 表示 n 在十進位表示時各位十進位數字的總和。可以很容易就看到,數列 $n, f(n), f(f(n)), f(f(f(n))), \cdots$ 最終會變成一個單一數字,且這數字會永遠重複下去。令這個個位數字記為 $g(n)$ 。 如考慮 $n = 1234567892$ ,則: $$f(n) = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 2 = 47$$ $$f(f(n)) = 4 + 7 = 11$$ $$ f(f(f(n))) = 1 + 1 = 2$$ 因此, $g(1234567892) = 2$ 。 解題思路: 這個一看就是遞迴了,具體請見範例程式碼: ```cpp= #include <bits/stdc++.h> using namespace std; int g(long long n){ if (n < 10) return n; int sum = 0; while (n > 0){ int digit = n % 10; sum += digit; n /= 10; } return g(sum); } int main(){ long long n; while (cin >> n && n){ cout << g(n) << '\n'; } return 0; } ```