【個人筆記】C++ ZeroJudge 基礎題庫解題(純練習) - part 1 === a003.:https://zerojudge.tw/ShowProblem?problemid=a003 難度:☆☆☆☆☆ 基本IO、條件判斷、四則運算 ```cpp= #include <iostream> using namespace std; int main(){ int M, D, S; cin >> M >> D; S = (M*2+D)%3; if (S==0){ cout << "普通"; } else if (S==1){ cout << "吉"; } else{ cout << "大吉"; } return 0; } ``` --- a004.:https://zerojudge.tw/ShowProblem?problemid=a004 難度:★☆☆☆☆ 遇到需要持續輸入的迴圈,在 CPP 中使用 `while (cin >> a)`。 若將 `while` 改成 `while (true)`,然後 cin 放裡面,會造成 TLE(Time Limit Exceed)時間超過限制。 使用 `while (cin >> a)` 可避免此情形發生,這行的意義為「迴圈會持續執行,直到 cin >> year 失敗為止」。 若輸入有效的整數時,條件為 true,迴圈繼續執行;若輸入了非整數,則 cin 錯誤,條件為 false,迴圈結束。 ```cpp= #include <iostream> using namespace std; int main(){ int year; while (cin >> year){ if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)){ cout << "閏年" << endl; } else{ cout << "平年" << endl; } } return 0; } ``` --- a005.:https://zerojudge.tw/ShowProblem?problemid=a005 難度:★★☆☆☆ 雙層迴圈、條件判斷、邏輯應用。 ```cpp= #include <iostream> using namespace std; int main(){ int t, a[5]; cin >> t; for (int i=0;i<t;i++){ for (int j=0;j<4;j++){ cin >> a[j]; } if ((a[1] - a[0]) == (a[2] - a[1])){ a[4] = a[3] + (a[1] - a[0]); } else{ a[4] = a[3] * (a[1] / a[0]); } for (int m=0;m<5;m++){ cout << a[m] << " "; } cout << "\n"; } return 0; } ``` --- a009.:https://zerojudge.tw/ShowProblem?problemid=a009 難度:★★☆☆☆ 題目敘述在講的就是凱薩密碼(Caesar Cipher),要找 K(位移數),可能需要參照一下 ASCII code。 註:其實也不用參照,只要透過程式的 char() 轉整數去相減就好了。 ![image](https://hackmd.io/_uploads/rylBPOAv1g.png) Source:https://shihyu.github.io/books/apas01.html 然後題目是叫你自己去範例輸出輸入找 K,我們只要看第一個字元就好了: ``` 範例輸入: 1JKJ'pz'{ol'{yhklthyr'vm'{ol'Jvu{yvs'Kh{h'Jvywvyh{pvu5 ``` ``` 範例輸出: *CDC is the trademark of the Control Data Corporation. ``` 可發現字元 `'1'`、字元 `'*'` 兩個的 ASCII Code DEC(十進位) 相差 7(49-42),所以 K 是 7。 然後就可以打 code 了: ```cpp= #include <iostream> #include <cstring> using namespace std; int main(){ string a; getline(cin , a); for (int i = 0;i<a.size();i++){ // a.size() 方法是表示 a 字串的總長度。 cout << char(a[i]-7); } return 0; } ``` 為何不用 `cin.get(char)`? `cin.get(char)` 只能一個一個去讀字元,而 `getline(cin, string)` 可以讀完整行字串。 另外 `cin.get(char)` 會保留 new-line 跳脫字元(`\n`)在輸入流中,若下次再次讀取,可能會讀到 `\n`。 `getline(cin, string)` 可以讀完整行字串,直到遇到 `\n` 為止。他會把整行字串存在 string 物件裡面,不會自行將 `\n` 包含在內。 總之 `cin.get(char)` 有可能會遇到無限迴圈的情形,但另一個不會。 那是不是看起來 `cin.get(char)` 比較沒啥屁用?錯。 :::success `cin.get(char)` 使用情形: 1. 讀逐個字元 2. 保留 `\n` 3. 處理跳脫字元 4. 限制輸入長度:`cin.get(char array, size)` 像是可控制陣列長度。 ::: :::success `getline()` 使用情形: 1. 需忽略 `\n` 2. 有空格的字串 ::: --- a010.:https://zerojudge.tw/ShowProblem?problemid=a010 難度:★★★★☆ ```cpp= #include <iostream> #include <map> using namespace std; void pf(int n) { map<int, int> factor; while (n % 2 == 0) { // 處理質因數 2(因為偶數的因數一定有 2) factor[2]++; n /= 2; } for (int i = 3; i * i <= n; i += 2) { // 用 for 迴圈處理奇數的質因數 // 若 i 平方超過 n 表示處理完畢 while (n % i == 0) { factor[i]++; n /= i; } } if (n > 1) { // 若 n 是質數且 > 1 factor[n]++; } // 輸出結果 bool first = true; // 此 Variable 用來控制是否輸出 * 來分隔因數 for (auto &[prime, exp] : factor) { // C++ 11 才有的 range-based for loop 用來遍歷 map // auto -> 自動偵測 map 的鍵值對型態 if (!first) cout << " * "; first = false; cout << prime; if (exp > 1) cout << "^" << exp; } // prime -> 因數的英文, exp -> 指數的英文 } int main() { int n; cin >> n; pf(n); return 0; } ``` 程式碼當中用到 C++ map 容器,可直接將其視為 Python 中的字典。 要宣告一個 map 容器,如下: ```cpp= std::map<key_type, value_type> myMap; ``` key_type:鍵的型態。 value_type:值的型態。 插入元素: ```cpp= myMap[key] = value; ``` 存取元素: ```cpp= value = myMap[key]; ``` 遍歷 Map: ```cpp= for (std::map<key_type, value_type>::iterator it = myMap.begin(); it != myMap.end(); ++it) { std::cout << it->first << " => " << it->second << std::endl; } ``` 來源:https://www.runoob.com/cplusplus/cpp-libs-map.html :::success 在此使用 Map 是為了方便計算跟儲存質因數(當Key)、次方數(當Value)。 ::: --- a013.:https://zerojudge.tw/ShowProblem?problemid=a013 難度:★★★★★ ```cpp= #include <bits/stdc++.h> // 此為萬用標頭檔 因為這支程式碼太多函式褲要導入了 using namespace std; // 羅馬數字與十進位映射 map<char, int> roman_to_int = { {'I', 1}, {'V', 5}, {'X', 10}, {'L', 50}, {'C', 100}, {'D', 500}, {'M', 1000}, }; // 羅馬數字轉為十進位 int R_T_I(const string &s) { int num = 0; int prev_value = 0; for (int i = s.size() - 1; i >= 0; --i) { int value = roman_to_int[s[i]]; if (value >= prev_value) { num += value; } else { num -= value; } prev_value = value; } return num; } // 十進位轉羅馬數字 string I_T_R(int num) { if (num == 0) return "ZERO"; pair<int, string> roman_values[] = { {1000, "M"}, {900, "CM"}, {500, "D"}, {400, "CD"}, {100, "C"}, {90, "XC"}, {50, "L"}, {40, "XL"}, {10, "X"}, {9, "IX"}, {5, "V"}, {4, "IV"}, {1, "I"}, }; string result; for (const auto &[value, symbol] : roman_values) { while (num >= value) { num -= value; result += symbol; } } return result; } int main() { string a, b; while (cin >> a && a != "#") { cin >> b; int num1 = R_T_I(a); int num2 = R_T_I(b); int diff = abs(num1 - num2); cout << I_T_R(diff) << endl; } return 0; } ``` :::success `string s` v.s. `string &s` v.s. `const string &s`: 三種不同函數傳遞方式各有不同: `string s`:傳值呼叫,效能差,如果資料量小可這樣做。 `string &s`:傳參考呼叫,效能略勝上述。另外要注意不能傳遞字面常數(`const char*`),因為此時函數傳遞方式是可被修改的,不相通。 `const string &s`:不能修改,但三者當中效能最好,反正就是可讀不能改,符合上述競程題目需求。 ::: 另外在函數 `int R_T_I(const string &s)` 當中,採用從右至左遍歷的解題思路。 因為在羅馬數字的規則下,「大數在前小數在後」代表加法 (如 VI = 6);「小數在前大數在後」代表減法 (如 IV = 4)。 所以這樣的作法可以比較好去判斷加減法的部分,以求確切的十進位數字。 最後輸出時,根據題目輸出要求,所以還是要把數字變回去羅馬數字,那這支程式碼就定義了函數 `string I_T_R(int num)`。 此函數使用到了 pair 容器,需要使用此標頭檔 `#include <utility>` 引入。(不過我們用了萬用標頭檔所以不用手動引入) --- 以下是有關於 pair 的簡介: 建立一個 myPair 物件: ```cpp= // 此行意義為 "將整數與字串關聯在一起" pair<int, string> myPair(123, "abc"); ``` 看到物件就會想到類別,對,沒錯,pair 本身是類別。而 pair 內部有兩個 public 的 member:first、second,可用於存取 pair 的值。 如下: ```cpp= #include <bits/stdc++.h> using namespace std; int main(){ pair <int, string> myPair(123, "abc"); // 123 為 firsst, "abc" 為 second cout << myPair.first << endl; cout << myPair.second << endl; return 0; } ``` 輸出結果: ``` 123 abc ``` --- 回到正題,在函數 `string I_T_R(int num)` 中,為何使用 pair 容器而不是 map? :::success 因為 map 內的鍵值對會自動做升序(由小到大遞增排序),並不符合羅馬數字的「減法規則」,而使用 pair 則不會更動順序,能自行調整,所以便使用 pair。 ::: 最後 ```cpp= string result; for (const auto &[value, symbol] : roman_values) { while (num >= value) { num -= value; result += symbol; } } ``` 這個部分,至於使用到 `const auto &[value, symbol]` 的目的,在於這東西是固定不動的(裡面是既定的羅馬數字對應十進位數字),不會被修改,所以給他加上 const。還有個好處就是帶來效能提升跟避免不必要的錯誤(如被修改到)。 另外加上 `&` 是因為 range-based for 內容物需要被修改到。