D146【模板】Kruskal 重构树 U201076 [Bzoj3732] Network
D146【模板】Kruskal 重构树 U201076 [Bzoj3732] Network_哔哩哔哩_bilibili
Kruskal 重构树
问题:无向图,n 个点,m 条带权边,q 次询问,每次询问:从 x 走到 y 的所有路径上最大边权的最小值。
构建
1. 并查集初始时,$n$ 个节点各自独立成一个集合
2. 对无向图跑 Kruskal,从小到大选边,每次选边设为 $(x,y,w)$,做以下操作:
(1)新建一个节点 $p$,点权等于选边的边权 $w$
(2)将 $x,y$ 的根节点均指向新建点 $p$,合并成一个集合(并查集操作)
(3)将 $x,y$ 的根节点均与点 $p$ 连无向边(建重构树)
在进行 $n-1$ 轮之后,得到一棵恰有 $n$ 个叶子的二叉树,同时每个非叶子节点恰好有两个儿子,这棵树就叫 Kruskal 重构树

应用
1. 求原图上从 $x$ 到 $y$ 所有路径上最大边权的最小值
方法:将边权升序排序,则原图上从 $x$ 到 $y$ 所有路径上最大边权的最小值 $=$ Kruskal 重构树上 $LCA(x,y)$ 的权值
例如:原图 $1,2$ 两点之间的简单路径有 $1-2$ 和 $1-3-2$,最大边权的最小值为 $2$,等于节点 $6$ 的点权
证明:原图 $x$ 到 $y$ 所有路径上最大边权的最小值 $=$ 最小生成树上 $x$ 到 $y$ 的路径上的边权最大值,否则与最小生成树矛盾
而这个边权最大值在选边时,恰好赋值给了新建点(即 $LCA(x,y)$)的权值
2. 求原图上从 $x$ 出发只经过边权 $\le val$ 的边能到达的节点数
方法:将边权升序排序,在 Kruskal 重构树上从 $x$ 做树上倍增,找到点权 $\le val$ 的节点 $p$,则答案 $=p$ 子树内的叶子数-1
例如:原图上从 $1$ 出发只经过边权 $\le 3$ 的边能到达的节点数为 $2$,即重构树节点 $6$ 的子树内的叶子数-1
从叶到根的点权是升序的,类似大根堆
3. 求原图上从 $x$ 到 $y$ 所有路径上最小边权的最大值
方法:将边权降序排序,则原图 $x$ 到 $y$ 所有路径上最小边权的最大值 $=$ Kruskal 重构树上 $LCA(x,y)$ 的权值
注意:跑最大生成树选边,做 Kruskal 重构树,如图

4. 求原图上从 $x$ 出发只经过边权 $\ge val$ 的边能到达的节点数
从叶到根的点权是降序的,类似小根堆
相关板子:
D08【模板】最小生成树 Kruskal 算法 - 董晓 - 博客园
U201076 [Bzoj3732] Network - 洛谷
// Kruskal 重构树 O(MlogM+NlogN) #include<bits/stdc++.h> using namespace std; const int N=30005,M=60005; //重构树 点数=2N,边数=4N int idx,h[N],to[M],ne[M]; void add(int u,int v){ to[++idx]=v;ne[idx]=h[u];h[u]=idx; to[++idx]=u;ne[idx]=h[v];h[v]=idx; } int n,m,q,p,val[N]; //val新建点点权 struct E{int x,y,w;}e[M]; //原图边 int pa[N]; //并查集数组 int dep[N],fa[N][21]; //树上倍增数组 int find(int x){ //并查集查找 return pa[x]==x?x:pa[x]=find(pa[x]); } void kruskal(){ for(int i=1;i<=n;++i) pa[i]=i; sort(e+1,e+1+m,[&](E a,E b){return a.w<b.w;}); for(int i=1;i<=m;++i){ int x=find(e[i].x),y=find(e[i].y); if(x!=y){ val[++p]=e[i].w; //新建点点权=边权 pa[p]=pa[x]=pa[y]=p; //点x,y均指向点p add(p,x); add(p,y); //x,y均与p连无向边 } } } void dfs(int x,int f){ //预处理dep,fa数组 dep[x]=dep[f]+1; fa[x][0]=f; for(int i=1;i<=20;i++) fa[x][i]=fa[fa[x][i-1]][i-1]; for(int i=h[x];i;i=ne[i]){ int y=to[i]; if(y!=f) dfs(y,x); } } int lca(int x,int y){ //倍增求lca if(dep[x]<dep[y]) swap(x,y); //让x更深 for(int i=20;i>=0;i--)if(dep[fa[x][i]]>=dep[y]) x=fa[x][i]; //x向上跳到y的同一层 if(x==y) return x; for(int i=20;i>=0;i--)if(fa[x][i]!=fa[y][i]) x=fa[x][i],y=fa[y][i]; //x,y一起向上跳 return fa[x][0]; } int main(){ ios::sync_with_stdio(0);cin.tie(0); cin>>n>>m>>q; for(int i=1,u,v,w;i<=m;++i){ cin>>u>>v>>w; e[i]={u,v,w}; } p=n; //新建点编号初值 kruskal();//重构树 dfs(p,0); //倍增预处理dep,fa数组 for(int u,v;q--;){ cin>>u>>v; printf("%d\n",val[lca(u,v)]); } }
浙公网安备 33010602011771号