Kruskal重构树
最小生成树
将一个图的带权边删掉若干条,使得图最终形成一棵树,并且满足边权之和最小。
kruskal
若我们要求最小生成树,我们贪心的从边权最小的边开始讨论,借用并查集,判断该边能否加入树中。若可以加入树中,则加入。
Kruskal重构树
过程:
kruskal算法执行的过程中,如果加入了第$i$条边,我们得到一个新节点$x$,$x$的点权为$i$号边的边权,将$x$与两个端点的$father$相连。最终,我们将得到一颗 拥有$2n-1$个节点的树,即Kruskal重构树。
性质:
1. 原本图中的$n$个节点,均为树上的叶子节点;
2. 重构树是一个大根堆(按最小生成树重构);
3. 重构树(按最小生成树重构)中,任意两个节点$a,b$的,在原图上的路径中的最大边权的最小值为$LCA(a,b)$的点权;
4. 若原图不连通,会得到重构树森林;
5. 重构树的节点总数为$2n-1$,它是一个二叉树。
例题:
简要题意:
给出一个图,求$x$到$y$的路径中最小边权的最大值。
分析:
我们要求最小边权的最大值,可以按最大生成树的原则建立Kruskal重构树。
询问一对点$(x, y)$,$LCA(x, y)$的点权即答案。

#include<bits/stdc++.h> using namespace std; #define re register int const int N=5e5+5; vector<int>G[N]; // 重构树 int fa[N][30], dep[N]; void dfs(int x) // 重构树lca初始化 { dep[x] ++; for(re i=1;i<=20;++i) fa[x][i] = fa[fa[x][i-1]][i-1]; for(re v : G[x]) { fa[v][0] = x; dep[v] = dep[x]; dfs(v); } } int lca(int x, int y) { if(dep[x] < dep[y]) swap(x, y); for(re i=0, d=dep[x]-dep[y];i<=20;++i) if(d & (1<<i)) x = fa[x][i]; if(x == y) return x; for(re i=20;i>=0;--i) if(fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return fa[x][0]; } struct node{int x, y, v;}A[N]; bool cmp(node x, node y){return x.v > y.v;} int total, n, m, f[N], val[N]; int getf(int x){return f[x]==x?x:f[x]=getf(f[x]);} void kruskal() { for(re i=1, nn=(n<<1);i<=nn;++i)f[i]=i; // 初始化并查集 sort(A+1, A+1+m, cmp); int k = 0; total = n; while(k < m) { // Kruskal int v = A[++k].v; int f1 = getf(A[k].x); int f2 = getf(A[k].y); if(f1 != f2) { val[++total] = v; // 新节点的点权即为该边的边权 G[total].push_back(f1); // 将新节点连向两端点的father G[total].push_back(f2); f[f1] = total; f[f2] = total; } } } int main() { scanf("%d%d",&n,&m); for(re i=1;i<=m;++i) { int x, y, z; scanf("%d%d%d",&x,&y,&z); A[i] = (node){x, y, z}; } kruskal(); dfs(total); scanf("%d",&m); while(m--) { int x, y; scanf("%d%d",&x,&y); if(getf(x) != getf(y)) puts("-1"); // 不连通 else printf("%d\n", val[lca(x, y)]); } return 0; }
例题二:删边(nkoj P8437)
简要题意:
已知一个图,每次询问图中$ki$个不同的点,我们求最小的$Ans$,使得将图中所有边权小于等于$Ans$的边删除后,这$ki$个点互不连通。
分析:
删除小于等于$Ans$的边,且我们要求$Ans$的最小值,可以按最大生成树的原则建立Kruskal重构树。
构完树后,任意两点$x,y$的$LCA$的点权,即在原图中两点路径上最小边权的最大值。也就是说,我们删除小于等于$val[LCA(x, y)]$的边后,$(x, y)$肯定不连通。
这里有两种想法:
1. 求$ki$个点的公共LCA,但画个图我们可以知道,这样不一定正确;
2. $ki$个点两两的$LCA$的点权的最大值。
很明显,第二种想法才是正确的。
但是如果我们真的两两都要求一遍$LCA$,然后求各个$LCA$的点权的最大值,这耗时$O(k2)$,很不优秀。
结合图我们可以知道,dfs序相近的两点,可以得到深度较大的$LCA$,深度越大,该点的点权越大,更有可能是$Ans$的值。
所以我们的Ans=max(val[LCA(x1,x2)], val[LCA(x2, x3)], ......)。

#include<stdio.h> #include<vector> #include<algorithm> using namespace std; #define re register int static char buf[1000000],*p1=buf,*p2=buf; #define getchar() p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++ inline int read(){ int x=0; char c=getchar(); while(c<'0'||c>'9')c=getchar(); while(c>='0'&&c<='9')x=x * 10 + c - 48,c=getchar(); return x; } const int N=2e6+5; int n, m; vector<int>G[N]; int f[N], val[N], total; struct node{int x, y, v;}A[N]; bool cmp(node x, node y){return x.v>y.v;} int getf(int x){return x==f[x]?x:f[x]=getf(f[x]);} void kruskal() { for(re i=1, nn=(n<<1);i<=nn;++i)f[i]=i; int k=0; total=n; sort(A+1, A+1+m, cmp); while(k<m) { k++; int f1=getf(A[k].x), f2=getf(A[k].y), v=A[k].v; if(f1 != f2) { val[++total]=v; f[f1]=total; f[f2]=total; G[total].push_back(f1); G[total].push_back(f2); } } } int a[N], in[N], dep[N], fa[N][30], dfn; void dfs(int x) { in[x]=++dfn; dep[x]++; for(re i=1;i<=20;++i)fa[x][i]=fa[fa[x][i-1]][i-1]; for(re v:G[x]) { dep[v]=dep[x]; fa[v][0]=x; dfs(v); } } int lca(int x, int y) { if(dep[x]<dep[y])swap(x, y); for(re i=0,d=dep[x]-dep[y];i<=20;++i)if(d&(1<<i))x=fa[x][i]; if(x==y)return x; for(re i=20;i>=0;--i) if(fa[x][i]!=fa[y][i]) x=fa[x][i], y=fa[y][i]; return fa[x][0]; } bool cmi(int x, int y){return in[x]<in[y];} int main() { int T; n=read(); m=read(); T=read(); for(re i=1;i<=m;++i) { int x=read(), y=read(), z=read(); A[i]=(node){x, y, z}; } kruskal(); dfs(total); int Ans=0, lastans=0; while(T--) { Ans=0; m=read(); for(re i=1;i<=m;++i) a[i]=lastans^read(); sort(a+1, a+1+m, cmi); // 按dfs序排序 for(re i=1;i<m;++i) Ans = max(Ans, val[lca(a[i], a[i+1])]); lastans = Ans; printf("%d\n", Ans); } return 0; }