---
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;
}
```
::: -->