# 第 14 周作業講解 [TOC] ## [2024 - 無聊的小明](https://neoj.sprout.tw/problem/2024/) (遞迴解) ### 解題思路 看個例子: - 排列 123 - 紀錄「可使用的數字」 - 一開始三個數字都可使用 - 選擇一個數字後就不能再使用 - 選擇 1 後可使用的數字就剩 2 3 - 每一層的選法都是從當前可使用的數字中小的開始選 - 選到沒有數字可以使用時就輸出 ![](https://i.imgur.com/SHQ50gX.png) 遞迴問題: - 大問題:排列 n 個可使用的數字 - 小問題:把一個數字選出來後,排列剩下的 n - 1 個數字 - 終止條件:沒有可使用的數字時,直接輸出 - 為了能輸出所選擇的數字,我們還要**按照順序**紀錄「目前選了哪些數字」 ### 程式框架 - 使用一個陣列來記錄可以使用的數字 - `usable[3]` 表示 3 這個數字可不可以使用 - 使用另一個陣列來記錄目前所選擇的數字 - 宣告一個遞迴函數,傳入「目前要排第幾位數字」和「最多可以選幾個數字」 - 這樣判端終止條件比較方便 - 為了方便,位數從左到右分別是 0、1、2、3... ```cpp bool usable[10] = {0}; // 可以使用的數字 int current_number[10] = {0}; // 目前所選擇的數字 void permutate(int cur_bit, int max_n) { } ``` ### 判斷終止條件 - 如果目前已經選了 n 個數字,那就可以直接輸出了,因為一定沒有數字可以選了 - 輸出時,如果第 0 位 (最左邊的數字) 是 0,就跳過不輸出,直接 return - 將 `current_number` 中的數字一個個輸出 ```cpp if (cur_bit == max_n) { if (current_number[0] == 0) return; for (int i = 0; i < max_n; i++) cout << current_number[i]; cout << endl; return; } ``` ### 拆分問題 - 從 0 開始找,如果當前的數字可以使用,就把它放到已選的數字中,並且標記爲不可使用 - 傳入下一層的函數,繼續排列後面的數字 - 下一層排完後,把當前的數字重新標記爲可以使用 ```cpp for (int i = 0; i < 10; i++) { if (usable[i]) { current_number[cur_bit] = i; // 放入目前所選擇的數字中 usable[i] = false; // 標記為不可使用 permutate(cur_bit + 1, max_n); // 排下一位 usable[i] = true; // 重新標記為可以使用 } } ``` ### 完整程式碼 ```cpp= #include <iostream> #include <vector> #include <algorithm> using namespace std; bool usable[10] = {0}; int current_number[10] = {0}; void permutate(int cur_bit, int max_n) { if (cur_bit == max_n) { if (current_number[0] == 0) return; for (int i = 0; i < max_n; i++) cout << current_number[i]; cout << endl; return; } for (int i = 0; i < 10; i++) { if (usable[i]) { current_number[cur_bit] = i; usable[i] = false; permutate(cur_bit + 1, max_n); usable[i] = true; } } } int main() { int n; cin >> n; for (int i = 0; i < n; i++) { int t; cin >> t; usable[t] = true; // 標記爲可以使用 } permutate(0, n); // 排第 0 位,最多排 n 個數字 } ``` ## [2024 - 無聊的小明](https://neoj.sprout.tw/problem/2024/) (STL解) ### 解題思路 - 標準函式庫中的 [next_permutation](https://en.cppreference.com/w/cpp/algorithm/next_permutation) 函數可以把一個 vector 中的數字轉成下一個排列,例如輸入的 vector 是 `{2, 1, 3}`,呼叫該函數可以把這個 vector 變成 `{2, 3, 1}` - 如果當前的排列是最後一個排列(如:`{3, 2, 1}`),該函數會回傳 false - 標準函式庫中的 [sort](https://en.cppreference.com/w/cpp/algorithm/sort) 函數可以把 vector 由小排到大 搭配上面提到的兩個函數,我們可以構思出這樣的解法: - 先把輸入由小到大排 - 不斷的輸出 vector 中的值並將它排成下一個排列 - 重複直到 next_permutation 回傳 false ### 程式框架 ```cpp sort(a.begin(), a.end()); do { if (a[0] == 0) continue; for (auto t : a) cout << t; cout << endl; } while (next_permutation(a.begin(), a.end())); ``` ### 完整程式碼 ```cpp= #include <iostream> #include <vector> #include <algorithm> using namespace std; int main() { int n; cin >> n; vector<int> a(n); for (auto &t : a) cin >> t; sort(a.begin(), a.end()); do { if (a[0] == 0) continue; for (auto t : a) cout << t; cout << endl; } while (next_permutation(a.begin(), a.end())); } ``` ## [448 - 麻將排序問題](https://neoj.sprout.tw/problem/448/) ### 解題思路 - 用數字來表示牌 - 排序只在乎大小關係,所以每張牌的數字你想怎麼定都可以,只要大小關係對了就行 - 可以把餅(B)、條(T)、萬(W)當成單位: - 1B = 1 - 1T = 10 - 1W = 100 - 大字 EE, SS, WW, NN, CC, FF, BB 就分別是 1000 ~ 7000 - 排序時就將牌名轉成數字後做比較 ### 程式框架 根據上面的邏輯,寫出一個函數來轉換牌名與數字: ```cpp int getLevel(string &str) { int level = 0; if (isdigit(str[0])) { int d = str[0] - '0'; switch (str[1]) { case 'T': d *= 10; break; case 'W': d *= 100; break; } level = d; } else { // 大字 int d = 1; switch (str[0]) { case 'E': break; case 'S': d = 2; break; case 'W': d = 3; break; case 'N': d = 4; break; case 'C': d = 5; break; case 'F': d = 6; break; case 'B': d = 7; break; } level = d * 1000; } return level; } ``` 而在排序時,可以使用 STL 的 sort 函數,為了讓他按照我們的意思排序,我們要自訂一個比較函數,用來比較兩個字串: ```cpp bool cmp(string &a, string &b) { return getLevel(a) < getLevel(b); } ``` - 這個比較函數回傳值的意義可以想成:當他回傳 true 時,a 在前、b 在後的順序是正確的 - 如果他回傳 false,代表 b 應該要放在 a 前面 那在呼叫 sort 函數時,把我們的 cmp 放第三個參數傳進去即可 ```cpp sort(cards.begin(), cards.end(), cmp); ``` ### 完整程式碼 ```cpp= #include <iostream> #include <vector> #include <string> #include <algorithm> using namespace std; int getLevel(string &str) { int level = 0; if (isdigit(str[0])) { int d = str[0] - '0'; switch (str[1]) { case 'T': d *= 10; break; case 'W': d *= 100; break; } level = d; } else { int d = 1; switch (str[0]) { case 'E': break; case 'S': d = 2; break; case 'W': d = 3; break; case 'N': d = 4; break; case 'C': d = 5; break; case 'F': d = 6; break; case 'B': d = 7; break; } level = d*1000; } return level; } bool cmp(string &a, string &b) { return getLevel(a) < getLevel(b); } int main() { int n; cin >> n; vector<string> cards; cards.resize(n); for (int i = 0; i < n; i++) { cin >> cards[i]; } sort(cards.begin(), cards.end(), cmp); for (int i = 0; i < n; i++) { cout << cards[i]; if (i != n - 1) cout << ' '; } cout << endl; } ```