Algorithm Note 7 Kruskal重构树算法/再谈NOIP2013货车运输
//对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; }