### p1. 成績指標 [題目連結](https://zerojudge.tw/ShowProblem?problemid=b964) 我們定義分數 $\ge 60$ 為 $x$ 集合,分數 $< 60$ 為 $y$ 集合 我們將其排序並從 $x$ 找出最小值 $y$ 找出最大值 如果 $x$ 沒有元素,代表沒有最小的及格成績,即為 wrost case 如果 $y$ 沒有元素,代表沒有最大的不及格成績,即為 best case ::: spoiler 程式碼 ```cpp #include <bits/stdc++.h> using namespace std; vector<int> m; int main(){ int n, minX = INT_MAX, maxY = INT_MIN; cin >> n; m.resize(n); for (auto &e: m){ cin >> e; if (e >= 60) minX = min(minX, e); else maxY = max(maxY, e); } sort(m.begin(), m.end()); for (auto i: m) cout << i << ' '; cout << '\n'; if (minX == INT_MAX) cout << maxY << '\n' << "worst case"; else if (maxY == INT_MIN) cout << "best case" << '\n' << minX; else cout << maxY << '\n' << minX << '\n'; } ``` ::: ### p2. 矩陣轉換 [題目連結](https://zerojudge.tw/ShowProblem?problemid=b965) 定義 $A$ 矩陣 $r$ 為行數 $c$ 為列數 ``` //值 座標(i, j) 1(0, 0), 1(0, 1) 1(1, 0), 3(1, 1) 2(2, 0), 1(2, 1) ``` 翻轉後 ``` 2(0, 0), 1(0, 1) 1(1, 0), 3(1, 1) 1(2, 0), 1(2, 1) ``` 旋轉後得 $B$ ``` 1(0, 0) 1(0, 1) 2(0, 2) 1(1, 0) 3(1, 1) 1(1, 2) ``` 透過觀察位置的變化就會發現其規律。 * 翻轉及其反操作 $A$ 經過翻轉 座標變化 $(i, j)$ -> $(r-i-1, j)$ :::info 這裡的操作次數為 $r/2$ 因為每次執行會交換到兩條行數 ::: * 旋轉及其反操作 $A$ 經過旋轉 座標變化 $(i, j)$ -> $(j, r-i-1)$ :::info 這裡的 $r$ 參數已經變為控制列的 $c$ 參數 因此要將兩者的值調換,避免 $r$ 與 $c$ 變成了錯誤的行列數 ::: 因題目要求因此實作時記得將操作改為反操作的形式 ::: spoiler 程式碼 ```cpp #include <bits/stdc++.h> using namespace std; int arr[10][10], r, c, m, ops; void flip_back(){ for (int i = 0; i < r/2; i++) for (int j = 0; j < c; j++) swap(arr[i][j], arr[r-i-1][j]); } void rotate_back(){ int tmp[10][10]; for (int i = 0; i < r; i++) for (int j = 0; j < c; j++) tmp[i][j] = arr[i][j]; swap(r, c); for (int i = 0; i < r; i++) for (int j = 0; j < c; j++) arr[i][j] = tmp[j][r-i-1]; } int main(){ cin >> r >> c >> m; for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) { cin >> arr[i][j]; } } while (m--) { cin >> ops; if (ops) flip_back(); else rotate_back(); } cout << r << ' ' << c << '\n'; for (int i = 0; i < r; i++) { for (int j = 0; j < c; j++) cout << arr[i][j] << ((j+1 == c)? "":" "); cout << '\n'; } } ``` ::: ### p3. 線段覆蓋長度 [題目連結](https://zerojudge.tw/ShowProblem?problemid=b966) 這題我們用貪心的角度去思考,如果將線段按左界排序,我們就可以逐步判斷線段覆蓋的區間。 我們定義當前最大左右界為 $L, R$ 單一線段的左右界為 $l, r$ 因為排序的關係所以我們可以保證新加入的線段 $l \ge L$。 * 如果新加入的線段 $r \le R$,那麼覆蓋長度則不變。 * 如果新加入的線段 $l \le R$,那麼代表說 $[ l, R ]$ 這個區間已經被覆蓋了,因此我們只需要新增未被覆蓋的區間 $[ R, r ]$。 * 如果新加入的線段 $l > R$,那麼代表說 $[ l, r ]$ 這塊是完全獨立的線段,我們只需新增它即可。 實作時記得每次都要更新當前最大右界 $R$ 。 複雜度:$O(n \log n)$ ::: spoiler 程式碼 ```cpp #include <bits/stdc++.h> using namespace std; #define pii pair<int, int> #define l first #define r second priority_queue<pii, vector<pii>, greater<pii>> pq; int main(){ int n, a, b; cin >> n; while (n--){ cin >> a >> b; pq.push({a, b}); } int mr = -1, result = 0; while (pq.size()) { pii t = pq.top(); pq.pop(); if (t.r <= mr) continue; result += (t.l <= mr)? (t.r - mr): (t.r - t.l); mr = t.r; } cout << result << '\n'; } ``` ::: ### p4. 血緣關係 [題目連結](https://zerojudge.tw/ShowProblem?problemid=b967) 這題算是圖論的經典題,問你圖上距離最遠的兩個點,即為所謂的樹直徑。 由於只有詢問一次,所以只要做兩次 Depth-First-Search 就行。 至於 DFS 的實作這裡就不多作介紹。 * 第一次 DFS 的時候先搜尋離當前節點最遠的節點。 * 第二次 DFS 的時候以第一次搜尋到的點作為起點搜尋最遠的節點。 複雜度:$O(n)$ ::: spoiler 程式碼 ```cpp #include <bits/stdc++.h> using namespace std; vector<vector<int>> g; int md = -1, mi = -1; void dfs(int idx, int father, int depth){ for (auto i: g[idx]){ if (i != father){ dfs(i, idx, depth+1); } } if (depth > md){ md = depth; mi = idx; } } signed main(){ int n, a, b; cin >> n; g.assign(n, {}); for (int i = 1; i < n; i++){ cin >> a >> b; g[a].pb(b); g[b].pb(a); } dfs(0, -1, 0); dfs(mi, -1, 0); cout << md << '\n'; } ``` :::