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 算法 - 董晓 - 博客园

D09【模板】最近公共祖先 倍增算法 - 董晓 - 博客园

 

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)]);
  }
}
 

 

题目列表 - 洛谷

 

posted @ 2026-04-28 08:31  董晓  阅读(213)  评论(0)    收藏  举报