--- tags: 成大高階競技程式設計 2021 image: https://i.imgur.com/IIj9BcL.png --- # 2021 高階競程 Contest #6 - 題解 ## [懶惰的旅人](https://judge.csie.ncku.edu.tw/problems/117) - Task Provider: D4nnyLee - Task Setter: D4nnyLee ### 首殺 team21_24 (00:04) ### 解法說明 這題因為道路的長度都是大於 $0$,所以其實 $x_i$ 就只是與 $i$ 之間有直達道路的所有城市中最近的那個城市。 ### 標準程式 :::spoiler ```cpp! #include <iostream> #include <vector> using namespace std; constexpr int inf{1000000010}; int main(void) { int n, m; cin >> n >> m; vector<pair<int, int>> ans(n, {inf, -1}); for (int i{0}; i < m; ++i) { int u, v, w; cin >> u >> v >> w; --u, --v; if (w < ans[v].first) ans[v] = {w, u}; if (w < ans[u].first) ans[u] = {w, v}; } for (int i{0}; i < n; ++i) cout << ans[i].second + 1 << ' '; cout << '\n'; return 0; } ``` ::: ## [踏溯成大](https://judge.csie.ncku.edu.tw/problems/118) - Task Provider: arashi87 - Task Setter: arashi87 ### 首殺 team21_24 (00:33) ### 解法說明 ![](https://i.imgur.com/KV4mZGR.png) 知道圖會是一棵樹後就會知道這題不能用單純的最短路通過,我們可以知道樹上最短路會唯一,因此假設我們要找 $x, y$ 的最短路徑的話可以先找出 $x$ 到根節點以及 $y$ 到根節點的距離相加,最後再扣掉 $2\times dis(LCA(x, y))$ 就會是答案,扣兩次的原因是因為 $LCA(x, y)$ 到根節點這段被重複算到兩次,它並不會被包含在最短路裡,因此需要扣掉,距離計算的部分則是可以在 dfs 時同步完成。 ### 標準程式 :::spoiler ```cpp #include <iostream> #include <vector> #include <utility> using namespace std; class LCA { const vector<vector<pair<int, long long>>>& adj; int n; vector<int> d; vector<int> log2; vector<vector<int>> an{}; void dfs(int u, int w = -1, int dep = 0) { d[u] = dep; an[u][0] = w; for (int i{1}; i <= log2[n - 1] && an[u][i - 1] != -1; ++i) an[u][i] = an[an[u][i - 1]][i - 1]; // 走 2^(i-1) 再走 2^(i-1) 步 // 因為計算 an 會用到祖先的資訊,所以先計算再繼續往下 for (auto& [v, _] : adj[u]) { if (v == w) continue; // parent dfs(v, u, dep + 1); } } public: LCA(const vector<vector<pair<int, long long>>>& _adj, int root) : adj{_adj}, n{adj.size()}, d(n), log2(n) { log2[1] = 0; for (int i{2}; i < log2.size(); ++i) log2[i] = log2[i / 2] + 1; an.assign(n, vector<int>(log2[n - 1] + 1, -1)); dfs(root); } int operator()(int u, int v) { if (d[u] > d[v]) swap(u, v); for (int i{log2[d[v] - d[u]]}; i >= 0; --i) if (d[v] - d[u] >= (1 << i)) v = an[v][i]; // v 先走到跟 u 同高度 if (u == v) return u; for (int i{log2[d[u]]}; i >= 0; --i) if (an[u][i] != an[v][i]) u = an[u][i], v = an[v][i]; // u, v 一起走到 lca(u, v) 的下方 return an[u][0]; // 回傳 lca(u, v) } }; class DIS { const vector<vector<pair<int, long long>>>& adj; int n; vector<long long> dis; void dfs(int u, int w = -1, long long d = 0) { dis[u] = d; for (auto& [v, len] : adj[u]) { if (v == w) continue; dfs(v, u, d + len); } } public: DIS(const vector<vector<pair<int, long long>>>& _adj, int root) : adj{_adj}, n{adj.size()}, dis(n) { dfs(root); } long long operator()(int v) { return dis[v]; } }; void solve() { int n, m; cin >> n >> m; vector<vector<pair<int, long long>>> adj(n); for (int i{0}; i < n - 1; ++i) { int u, v; long long w; cin >> u >> v >> w, --u, --v; adj[u].emplace_back(v, w); adj[v].emplace_back(u, w); } int r{0}; LCA lca{adj, r}; DIS dis{adj, r}; while (m--) { int u, v; cin >> u >> v, --u, --v; cout << dis(u) + dis(v) - 2 * dis(lca(u, v)) << '\n'; } } int main() { ios_base::sync_with_stdio(false); cin.tie(nullptr); int t{1}; //cin >> t; while (t--) solve(); return 0; } ``` ::: ## [紅心 7 的三角棋盤](https://judge.csie.ncku.edu.tw/problems/119) - Task Provider: raiso - Task Setter: raiso ### 首殺 team21_24 (01:43) ### 解法說明 本題只想要測試各位的 dfs 能力,以及三角形棋盤的聯通性。 唯一需要注意的就是「每個 row 的邊界都不一樣大」 ### 標準程式 :::spoiler ```cpp #include <bits/stdc++.h> using namespace std; int l; bool G[5][5] = {}; bool onBoard(int a, int b) { if(a < l && a >= 0 && b <= a && b >= 0) return 1; return 0; } bool dfs(int n) { if(n == 1) return 1; for(int r = 0; r < l; r++) for(int c = 0; c <= r; c++) if(G[r][c]) { for(int dr = -1; dr <= 1; dr++) for(int dc = -1; dc <= 1; dc++) if(dr+dc != 0) { int r1 = r+dr, c1 = c+dc; int r2 = r+dr*2, c2 = c+dc*2; if(onBoard(r2, c2) && G[r1][c1] && !G[r2][c2]) { G[r2][c2] = 1, G[r1][c1] = 0, G[r][c] = 0; if(dfs(n-1)) return 1; G[r2][c2] = 0, G[r1][c1] = 1, G[r][c] = 1; } } } return 0; } int main() { int n; cin >> n >> l; for(int i = 0; i < n; i++) { int a, b; cin >> a >> b, a--, b--; G[a][b] = 1; } bool ans = dfs(n); cout << (ans? "Yes": "No") << endl; return 0; } ``` ::: ## [高速公路維修站](https://oj.leo900807.tw/problems/120) - Task Provider: baluteshih - Task Setter: leo ### 首殺 team21_12 (01:49) ### 解法說明 <font color="red"><B>本題賽後加強測資,不影響賽中 Submission。</B></font> 看到值域可以很明顯的知道從每個點做一次最短路徑是行不通的, 不如我們換個想法,一個點到最近的維修站距離, 也可以視為從所有的維修站同時出發, 看哪個維修站先走到這個點,即為該點到最近維修站的距離。 那麼我們就同時從所有維修站出發,也就是做多個起點的最短路徑, 但是做 $F$ 次最短路又太耗費時間,因此可以採用一個方法, 我們使用一個「超級起點」, 且將超級起點連到所有的維修站,距離為 $0$, 則只要做一次從超級起點出發的最短路徑, 可以直接使用 dijkstra 演算法求解。 ### 標準程式 :::spoiler ```cpp #include <iostream> #include <algorithm> #include <queue> #include <vector> #include <bitset> #include <cstring> using namespace std; struct edge{ int to; long long w; bool operator<(const edge &a)const{ return w > a.w; } }; vector<edge> v[100001]; priority_queue<edge> q; bitset<100001> vis; long long dis[100001]; int main() { ios::sync_with_stdio(0); cin.tie(0); int n, m, f, st, ed, w, f_i; cin >> n >> m >> f; memset(dis, -1, sizeof(dis)); for(int i = 0; i < m; i++){ cin >> st >> ed >> w; v[st].push_back(edge{ed, w}); v[ed].push_back(edge{st, w}); } for(int i = 0; i < f; i++) cin >> f_i, q.push(edge{f_i, 0}); while(!q.empty()){ edge tmp = q.top(); q.pop(); if(vis[tmp.to]) continue; vis[tmp.to] = 1; dis[tmp.to] = tmp.w; for(edge i : v[tmp.to]) if(!vis[i.to]) q.push(edge{i.to, tmp.w + i.w}); } for(int i = 1; i <= n; i++) cout << dis[i] << " \n"[i == n]; } ``` ::: ## [勞贖🐭](https://judge.csie.ncku.edu.tw/problems/121) - Task Provider: ys - Task Setter: ys ### 首殺 team21_24 (01:30) ### 解法說明 - 設沒有捕鼠器的路為 $0$ 權重的邊 - 而含有捕鼠器的路為 $1$ 權重的邊 目標是要找出**總權重最小**的**連通**生成子圖 > 連通的生成子圖就是題目要求的地圖 首先,子圖中並沒有任何的邊,可以看做子圖有 $N$ 個連通塊 > 一個目標要將這 $N$ 個連通塊通通相連,使得只剩 $1$ 個連通塊 接著將 $0$ 權重的邊都放入子圖中,這樣不會增加總權重,且連通塊數量會變少 最後剩下的連通塊,只能透過 $1$ 權重的邊相連。這樣產生了總權重最小的連通圖 #### 解法 1 實作上,枚舉所有還未被 DFS 拜訪過的點 對於每一點將它能透過 $0$ 權重路徑到達的點都納入連通塊中 這部分透過 DFS,將 $0$ 權重邊拜訪到的**鄰點**,一層層加入到連通塊中 當無法繼續進行 DFS,表示該連通塊已生成完畢 接著對於**剩下的點**進行一樣的操作。 #### 解法 2 同解法 1,可改成使用 BFS 進行點的拜訪 ### 標準程式 :::spoiler 解法 1 ```cpp #include<bits/stdc++.h> #define all(x) x.begin(), x.end() using namespace std; int constexpr maxn = 5e5 + 10; int n, m; set<int> g[maxn], rest; void dfs(int u) { vector<int> nbh(rest.size()); // neighbourhood auto it = set_difference(all(rest), all(g[u]), nbh.begin()); nbh.resize(it-nbh.begin()); for(auto v: nbh) rest.erase(v); for(auto v: nbh) dfs(v); } int main() { scanf("%d%d", &n, &m); for(int i = 0; i < m; i++) { int a, b; scanf("%d%d", &a, &b); g[a].insert(b); g[b].insert(a); } for(int i = 1; i <= n; i++) rest.insert(i); int ans = -1; for(int i = 1; i <= n; i++) { if(!rest.count(i)) continue; rest.erase(i); dfs(i); ans++; } printf("%d\n", ans); return 0; } ``` ::: :::spoiler 解法 2 ```cpp #include<bits/stdc++.h> #define all(x) x.begin(), x.end() using namespace std; int constexpr maxn = 5e5 + 10; int n, m, a, b; set<int> g[maxn]; int main() { scanf("%d%d", &n, &m); while(m--) { scanf("%d%d", &a, &b); g[a].insert(b); g[b].insert(a); } set<int> rest; for(int i = 1; i <= n; i++) rest.insert(i); int comp = 0; // component queue<int> nbh; // neighbourhood while(rest.size()) { if(nbh.empty()) { comp++; nbh.push(*rest.begin()); rest.erase(rest.begin()); } int u = nbh.front(); nbh.pop(); vector<int> diff(rest.size()); auto it = set_difference(all(rest), all(g[u]), diff.begin()); diff.resize(it-diff.begin()); for(int v: diff) rest.erase(v), nbh.push(v); } printf("%d\n", comp-1); return 0; } ``` ::: <!-- :::spoiler 解法 3 ```cpp #include<bits/stdc++.h> using namespace std; int constexpr maxn = 5e5 + 10; int n, m; int group[maxn], sz[maxn]; int Find(int v) { if (v == group[v]) return v; return group[v] = Find(group[v]); // Path Compression } void Union(int u, int v) { group[Find(u)] = Find(v); sz[Find(u)] += sz[Find(v)]; } int main() { scanf("%d%d", &n, &m); for (int v = 1; v <= n; v++) group[v] = v, sz[v] = 1; set<set<int>> S; vector<int> d[maxn]; for(int i = 0; i < m; i++) { int a, b; scanf("%d%d", &a, &b); S.insert({a, b}); d[a].push_back(b); d[b].push_back(a); } int p = 0; // privious for(int u = 1; u <= n; u++) { if(d[u].size() < n/2) { if(p) Union(p, u); p = u; } else { for(int v = 1; v <= n; v++) if(!S.count({u, v})) Union(u, v); } } set<int> comp; // component for(int i = 1; i <= n; i++) comp.insert(Find(i)); printf("%d\n", comp.size()-1); return 0; } ``` ::: -->