Algorithm Note 7 Kruskal重構樹演算法/再談NOIP2013貨車運輸
阿新 • • 發佈:2020-08-17
//對Kruskal演算法的介紹以sdsc課件為基礎,原作者為djh老師。
Kruskal 重構樹演算法是一種基於 Kruskal 的變形。
在使用並查集合並兩個集合 (兩棵樹) 的時候,我們新建一個點作為樹根,點權為連線兩個集合的邊的邊權。
我們考慮這樣得到的一棵樹會有什麼性質。
1. 原圖中的所有節點都是葉子。因為合併過程中只有新結點能作為父節點。
2. 總共有 O(n) 個節點。
3. 是一棵二叉樹。
4. 如果把葉子節點的權值看作 0,那麼滿足大根堆的性質。顯然,由於按邊權排序,新加入的點權大於一定兩個子結點。
5. 最小生成樹上兩點間路徑邊權的最大值是他們重構樹上LCA的點權大小。因為當兩個點首次在重構樹上聯通時也在原生成樹上聯通,它們之間路徑的最大值一定時當時的根,也就是LCA。
程式碼實現
1 for(int i = 1; i <= n; ++i) fa[i] = i; 2 sort(e + 1, e + 1 + m); 3 int node = n; 4 for(int i = 1; i <= m; ++i) { 5 int fu = getf(e[i].u), fv = getf(e[i].v); 6 if(fu == fv) continue; 7 val[++ node] = e[i].c; 8 fa[node] = fa[u] = fa[v] = node; 9 add(node, u); add(node, v);10 cnt ++; 11 if(cnt == n - 1) break; 12 }
然後課件上放了這個模板題和NOI2018 歸程。
不過模版題沒處交,歸程咋都調不出來,於是我想起以前寫的一個題,NOIP2013(洛谷P1967)貨車運輸。
https://www.luogu.com.cn/problem/P1967
題意:給定一張地圖,n個地點m條道路,每段道路有最大限重。q次詢問從u到v兩地之間貨車載重量的最大值,如果兩地不能互達輸出-1。
所以這不就是模板題嗎。。。
不過我還是調了很久。在sdsc剛養成all in stl的習慣,經常犯一些i <= g[u].size()之類的錯誤。
然後注意這題是個森林,不能直接dfs(1, 0),而應該
for (int i = 1; i <= cnt; i++) { if (!vis[i]) { int f = find(i); dfs(f, 0); } }
為什麼是對的呢?因為建重構樹的過程保證了find(i)為重構樹的根。
以下是完整程式碼。
#include <bits/stdc++.h> using namespace std; const int N = 2e6 + 6; struct edge { int u, v, w; bool operator < (const edge &x) { return w > x.w; } } e[N]; vector<int> g[N]; int n, m, q, cnt, fa[N], vis[N], val[N], d[N], f[N][30]; int find(int x) { return x == fa[x] ? x : fa[x] = find(fa[x]); } void kruskal() { sort(e + 1, e + m + 1); for (int i = 1; i <= n; i++) fa[i] = i; for (int i = 1; i <= m; i++) { int fu = find(e[i].u), fv = find(e[i].v); if (fu != fv) { val[++cnt] = e[i].w; fa[cnt] = fa[fu] = fa[fv] = cnt; g[cnt].push_back(fu); g[cnt].push_back(fv); g[fu].push_back(cnt); g[fv].push_back(cnt); } } } void dfs(int u, int fa) { d[u] = d[fa] + 1, f[u][0] = fa, vis[u] = 1; for (int i = 1; (1 << i) <= d[u]; i++) f[u][i] = f[f[u][i-1]][i-1]; for (int i = 0; i < g[u].size(); i++) { int v = g[u][i]; if (v != fa) dfs(v, u); } } int lca(int x, int y) { if (d[x] < d[y]) swap(x, y); int k = d[x] - d[y]; for (int i = 25; i >= 0; i--) if ((1 << i) & k) x = f[x][i]; if (x == y) return x; for (int i = 25; i >= 0; i--) if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i]; return f[x][0]; } int main() { cin >> n >> m; cnt = n; for (int i = 1; i <= m; i++) cin >> e[i].u >> e[i].v >> e[i].w; kruskal(); for (int i = 1; i <= cnt; i++) { if (!vis[i]) { int f = find(i); dfs(f, 0); } } cin >> q; while (q--) { int u, v; cin >> u >> v; if (find(u) != find(v)) cout << -1 << endl; else cout << val[lca(u, v)] << endl; } return 0; }