Kruskal重构树

//https://blog.csdn.net/hwzzyr/article/details/81190442

Kruskal重构树

用于解决图中,有关两点间路径最大值最小化或最小值最大化的问题
如将

 

 


按边权从小到大建立Kruskal重构树,我们就能得到这样的树

 

 


首先Kruskal重构树只有2N-1个节点,只有 N 到 2 N 的点才有权值
而原图中任意两点u,v间路径中最大边权的最小值可以在这颗树中找到,即
val[lca(u,v)]
并且这颗树是一个大根堆,父节点的值大于或等于子节点的值

同理,如果我们按边权从大到小建立Kruskal重构树,我们就能得到这样的树

 

 


原图中任意两点u,v间路径中最小边权的最大值可以在这颗树中找到,即val[lca(u, v)]
并且这颗树是一个小根堆,父节点的值小于或等于子节点的值
https://blog.csdn.net/weixin_44282912/article/details/105821573

 

 

# include <iostream>
# include <stdio.h>
# include <stdlib.h>
# include <algorithm>
# include <string.h>
# define IL inline
# define ll long long
# define Fill(a, b) memset(a, b, sizeof(a));
using namespace std;
 
IL ll Read(){
    char c = '%'; ll x = 0, z = 1;
    for(; c < '0' || c > '9'; c = getchar()) z = c == '-' ? -1 : 1;
    for(; c >= '0' && c <= '9'; c = getchar()) x = x * 10 + c - '0';
    return x * z;
}
 
const int MAXN = 20001, MAXM = 200001;
int ft[MAXN], n, m, cnt, fa[MAXN][20], w[MAXN], deep[MAXN], Fa[MAXN], num;
struct Edge{
    int to, nt;
} edge[MAXM];
struct Kruskal{
    int u, v, f;
    IL bool operator <(Kruskal b) const{
        return f > b.f;
    }
} road[MAXM];
 
IL int Find(int x){
    return Fa[x] == x ? x : Fa[x] = Find(Fa[x]);
}
 
IL void Add(int u, int v){
    edge[cnt] = (Edge){v, ft[u]}; ft[u] = cnt++;
    edge[cnt] = (Edge){u, ft[v]}; ft[v] = cnt++;
}
 
IL void Dfs(int u){
    for(int e = ft[u]; e != -1; e = edge[e].nt){
        int v = edge[e].to;
        if(!deep[v]){
            deep[v] = deep[u] + 1;
            fa[v][0] = u;
            Dfs(v);
        }
    }
}
 
IL int LCA(int u, int v){
    if(Find(u) != Find(v)) return -1;
    if(deep[u] < deep[v]) swap(u, v);
    for(int i = 18; i >= 0; i--)
        if(deep[fa[u][i]] >= deep[v]) u = fa[u][i];
    if(u == v) return w[u];
    for(int i = 18; i >= 0; i--)
        if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
    return w[fa[u][0]];
}
 
int main(){
    Fill(ft, -1);
    num = n = Read(); m = Read();
    for(int i = 1; i <= 2 * n; i++)
        Fa[i] = i;
    for(int i = 1; i <= m; i++)
        road[i] = (Kruskal){Read(), Read(), Read()};
    sort(road + 1, road + m + 1);
    for(int i = 1, tot = 0; i <= m && tot < n; i++){
        int u = Find(road[i].u), v = Find(road[i].v);
        if(u != v){
            tot++;
            w[++num] = road[i].f;
            Fa[u] = Fa[v] = num;
            Add(u, num); Add(v, num);
        }
    }
    for(int i = num; i; i--)
        if(!deep[i]) deep[i] = 1, Dfs(i);
    for(int i = 1; i <= 18; i++)
        for(int j = 1; j <= num; j++)
            fa[j][i] = fa[fa[j][i - 1]][i - 1];
    int Q = Read();
    while(Q--){
        int u = Read(), v = Read();
        printf("%d\n", LCA(u, v));
    }
    return 0;
}

 
Description

给你N个点的无向图 (1 <= N <= 15,000),记为:1…N。
图中有M条边 (1 <= M <= 30,000) ,第j条边的长度为:
d_j ( 1 < = d_j < = 1,000,000,000).

现在有 K个询问 (1 < = K < = 15,000)。
每个询问的格式是:A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?
Input

第一行: N, M, K。
第2..M+1行: 三个正整数:X, Y, and D (1 <= X <=N; 1 <= Y <= N). 表示X与Y之间有一条长度为D的边。
第M+2..M+K+1行: 每行两个整数A B,表示询问从A点走到B点的所有路径中,最长的边最小值是多少?
Output

 对每个询问,输出最长的边最小值是多少。
Sample Input
6 6 8
1 2 5
2 3 4
3 4 3
1 4 8
2 5 7
4 6 2
1 2
1 3
1 4
2 3
2 4
5 1
6 2
6 1
Sample Output
5
5
5
4
4
7
4
5
HINT

 

1 <= N <= 15,000

1 <= M <= 30,000

1 <= d_j <= 1,000,000,000

1 <= K <= 15,000

 

 

 

正解:最小生成树+倍增lca

解题报告:

  大概题意是给定一个无向图,然后求两点之间的路径中权值最大的边的最小值

  望着这道题10分钟之后感觉做不到一眼秒题,老老实实画图,结果发现我真是太弱了,居然没有发现满足题意的条件竟然是最小生成树的性质。。。

  显然先构出最小生成树,其他的边是没有用的,可以删掉。

  构出最小生成树之后,就考虑两点间的路径上的最大值。

  可以在求lca的时候顺便维护一下就可以了。

  我开始打了一个树链剖分+线段树,然而上午脑袋不是很清白,而且鬼畜的BZOJ,居然迷之RE了两次。

  好吧,被迫改用倍增,然后就过了,并不知道为什么树链剖分哪里打萎了。

  (这道题其实就是NOIP2013的原题货车运输的改版的好吗,几乎一模一样)

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<vector>
#include<queue>
#include<string>
#ifdef WIN32
#define OT "%I64d"
#else
#define OT "%lld"
#endif
using namespace std;
typedef long long LL;
int n,m;
int u[200011],to[200011],w[200011];
int first[200011],next[200011],u1[200011],to1[200011],ww[200011];
int father[200011],height[200011];
int f[100011][16],quan[100011][16];

inline int getint(){
    int q=0,w=0;
    char c=getchar();
    while(c!='-' && ( c<'0' || c>'9' ) ) c=getchar();
    if(c=='-') q=1,c=getchar();
    while(c>='0' && c<='9') w=w*10+c-'0',c=getchar();
    return q?-w:w;
}

inline void qsort(int l,int r)
{
      int i=l,j=r;
      int mid=w[(i+j)/2],p;
      do
      {
         while(w[i]<mid)i++;
         while(w[j]>mid)j--;
         if(i<=j)
         {
             p=w[i];w[i]=w[j];w[j]=p;
             p=u[i];u[i]=u[j];u[j]=p;
             p=to[i];to[i]=to[j];to[j]=p;
             i++;
             j--;
         }
      }while(i<=j);
      if(i<r)qsort(i,r);
      if(l<j)qsort(l,j);
}

inline int find(int x){
      if(father[x]!=x) father[x]=find(father[x]);
      return father[x];
}

inline void hebing(int x,int y){
       father[y]=x;
}

inline void dfs(int x,int deep){
     height[x]=deep;
     for(int i=1;i<=15;i++){
           f[x][i]=f[ f[x][i-1] ][i-1];
           quan[x][i]=max( quan[x][i-1],quan[ f[x][i-1] ][i-1] );
     }
     for(int i=first[x];i;i=next[i]){
          if(height[to1[i]]==0)
          {
            f[ to1[i] ][0]=x;
            quan[ to1[i] ][0]=ww[i];
            dfs(to1[i],deep+1);
          }
     }
}

int lca(int x,int y){
       if(height[x]<height[y]) { int t=x;x=y;y=t; }
       int t=0;
       while((1<<t) <=height[x]) t++;
       t--;
       int ans1=-0x7ffffff,ans2=-0x7ffffff;
       for(int i=t;i>=0;i--){
          if(height[x]-(1<<i)>=height[y]) {
              ans1=max(ans1,quan[x][i]);
              x=f[x][i];
          }
       }
       if(x==y) return ans1;
       for(int i=t;i>=0;i--){
          if(f[x][i]!=f[y][i]){
             ans1=max(ans1,quan[x][i]); ans2=max(ans2,quan[y][i]);
             x=f[x][i];y=f[y][i];
          }
       }
       int zong1,zong2;
       zong1=max(ans1,quan[x][0]);
       zong2=max(ans2,quan[y][0]);
       return max(zong1,zong2);
}

inline void work(){
    qsort(1,m);
    for(int i=1;i<=n;i++)    father[i]=i;
    int i=1,j=0;
    int yigong=0;
    while(i<=m){
       int r1=find(u[i]);int r2=find(to[i]);
       hebing(r1,r2);
       yigong++;
       j++; next[j]=first[u[i]]; first[u[i]]=j;ww[j]=w[i]; to1[j]=to[i]; u1[j]=u[i];
       j++; next[j]=first[to[i]]; first[to[i]]=j;ww[j]=w[i]; to1[j]=u[i]; u1[j]=to[i];
       i++;
       while(find(u[i])==find(to[i]) && i<=m)
           i++;
    }
    for(int i=1;i<=n;i++)
     if(father[i]==i)
     {
        dfs(i,1);
     }
}

int main()
{
    n=getint();m=getint();int q=getint();
    int ljh,jump,jumpjump;
    for(int i=1;i<=m;i++){
          ljh=getint();jump=getint();jumpjump=getint();
          u[i]=ljh;to[i]=jump;w[i]=jumpjump;
    }

    work();
    for(int i=1;i<=q;i++){
        ljh=getint();jump=getint();
        int ans=lca(ljh,jump);
        printf("%d\n",ans);
    }
    return 0;
}

 

#include<cctype>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 45005
#define F inline
using namespace std;
struct edge{ int x,y,z; }ed[N];
int n,m,k,q,nd,f[N],dep[N],fa[N][18],t[N][2],w[N];
F char readc(){
    static char buf[100000],*l=buf,*r=buf;
    if (l==r) r=(l=buf)+fread(buf,1,100000,stdin);
    return l==r?EOF:*l++;
}
F int _read(){
    int x=0; char ch=readc();
    while (!isdigit(ch)) ch=readc();
    while (isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=readc();
    return x;
}
F void writec(int x){ if (x>9) writec(x/10); putchar(x%10+48); }
F void _write(int x){ writec(x),puts(""); }
F bool cmp(edge a,edge b){ return a.z<b.z; }
int findfa(int x){ return x==f[x]?x:f[x]=findfa(f[x]); }
void dfs(int x){
    if (!x) return; dep[x]=dep[fa[x][0]]+1;
    dfs(t[x][0]),dfs(t[x][1]);
}
F void Make(){
    for (int j=1;j<18;j++)
        for (int i=1;i<n<<1;i++)
            fa[i][j]=fa[fa[i][j-1]][j-1];
}
F int LCA(int x,int y){
    if (dep[x]<dep[y]) swap(x,y);
    for (int j=17;~j;j--)
        if (dep[fa[x][j]]>=dep[y]) x=fa[x][j];
    if (x==y) return x;
    for (int j=17;~j;j--)
        if (fa[x][j]!=fa[y][j])
            x=fa[x][j],y=fa[y][j];
    return fa[x][0];
}
int main(){
    nd=n=_read(),m=_read(),q=_read();
    for (int i=1,x,y;i<=m;i++)
        x=_read(),y=_read(),ed[++k]=(edge){x,y,_read()};
    sort(ed+1,ed+m+1,cmp);
    for (int i=1;i<n<<1;i++) f[i]=i;
    for (int i=1,s=0,x,y,fx,fy;s<n-1;i++)
        if ((fx=findfa(x=ed[i].x))!=(fy=findfa(y=ed[i].y))){
            t[++nd][0]=fx,t[nd][1]=fy,w[nd]=ed[i].z;
            f[fx]=f[fy]=fa[fx][0]=fa[fy][0]=nd,s++;
        }
    for (dfs(nd),Make();q;q--) _write(w[LCA(_read(),_read())]);
    return 0;
}
//https://blog.csdn.net/a1799342217/article/details/81366780

 路径权值
Description
  给定一个带权树,树上任意两点间的路径权值d(x,y)定义为x,y这两个点之间路径上的最小值,
树上任意一点x的权值定义为这个点到树上其他所有点的路径权值和,
即Sigma(d(x,i)),1<=i<=N,现求树上一点,使得这个点的权值最大,输出这个值。
Input
首先输入一个整数Q,接着每组数据首先输入一个整数 n(1≤n≤100000),
表示该组数据中树的点的个数。
接下来n-1行,每行三个整数 x,y,s(1≤x,y≤n,1≤s≤1000),
表示编号为x的节点和编号为y的节点之间存在一条权值为s的边,树上每个点的编号为1 n
Output
对于每组数据,首先输出数据编号,然后输出树上的点的最大权值,具体格式见输出样例。
Sample Input
2
4
1 2 2
2 4 1
2 3 1
4
1 2 1
2 4 1
2 3 1
Sample Output
Case 1: 4
Case 2: 3

这道题目怎么做呢?
显然,如果我们暴力枚举点对是不可行的。
既然是和树上路径有关的问题,点分治可不可行呢?
本蒟蒻太菜了,根本想不到啊qwq
那我们试图算一下边的贡献,即一条边对所有经过这一条边的点对都会有这条边长度的贡献。
怎么算呢?
我们之前提到过Kruskal重构树中,两个节点的LCA节点就是两点路径上的最大/最小节点。
也就是说,对于一个非叶节点x,
它左子树中的节点到右子树中的节点的路径一定会经过x节点所对应的边,反之亦然。
那么我们就可以建出Kruskal重构树之后维护当前边对于重构树子树中节点的贡献了。
这个区间加法的过程我们可以用树状数组实现。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int Maxn=100005;
inline int read() {
    static char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int T,n;
struct Edge {int a,b,w;} e[Maxn];
inline bool operator < (const Edge &A,const Edge &B) {return A.w>B.w;}
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn<<1],cnt=0;
inline void add(int x,int y) {
    branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
int fa[Maxn<<1],val[Maxn<<1];
inline int getfa(int x) {return x==fa[x]?x:fa[x]=getfa(fa[x]);}
inline void Ex_Kruskal() {
    int ind=n,lim=n<<1; sort(e+1,e+n);
    for(int i=1;i<lim;++i) fa[i]=i;
    for(int i=1;i<n;++i) {
        int fx=getfa(e[i].a),fy=getfa(e[i].b);
        fa[fx]=fa[fy]=++ind;
        val[ind]=e[i].w;
        add(ind,fx); add(ind,fy);
    } return ;
}
int size[Maxn<<1],st[Maxn<<1],ed[Maxn<<1],idx;
inline void Dfs(int v) {
    size[v]=v<=n; st[v]=++idx;
    for(int i=h[v];i;i=branch[i].next)
        Dfs(branch[i].to),size[v]+=size[branch[i].to];
    ed[v]=idx;
}
struct Bit {
    int c[Maxn<<1];
    inline void reset(){memset(c,0,n<<3); return ;}
    inline void Insert(int x,int d) {while(x<=n<<1) c[x]+=d,x+=x&-x; return ;}
    inline int Ask(int x) {int rec=0; while(x>=1) rec+=c[x],x-=x&-x; return rec;}
}A;
int main() {
    T=read();
    for(int t=1;t<=T;++t) {
        cnt=0; idx=0; memset(h,0,4*(n<<1));
        n=read();
        for(int i=1;i<n;++i) {
            int x=read(),y=read(),z=read();
            e[i]=(Edge){x,y,z};
        }
        Ex_Kruskal(); Dfs((n<<1)-1);
        A.reset();
        for(int i=n+1;i<n<<1;++i) {
            int ls=0,rs;
            for(int k=h[i];k;k=branch[k].next) {
                if(ls) rs=branch[k].to;
                else ls=branch[k].to;
            }
            A.Insert(st[ls],val[i]*size[rs]);
            A.Insert(ed[ls]+1,-val[i]*size[rs]);
            A.Insert(st[rs],val[i]*size[ls]);
            A.Insert(ed[rs]+1,-val[i]*size[ls]);
        }
        int ans=0;
        for(int i=1;i<=n;++i) ans=max(ans,A.Ask(st[i]));
        cout<<"Case "<<t<<": "<<ans<<'\n';
    }
    return 0;
}
//https://blog.csdn.net/hwzzyr/article/details/81190442

 最后要提到的就是我们Kruskal重构树最常见的经典题目

【BZOJ3551】Peaks加强版
Description
在Bytemountains有N座山峰,每座山峰有他的高度h_i。有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询问从点v开始只经过困难值小于等于x的路径所能到达的山峰中第k高的山峰,如果无解输出-1。
Input
第一行三个数N,M,Q。
第二行N个数,第i个数为h_i
接下来M行,每行3个数a b c,表示从a到b有一条困难值为c的双向路径。
接下来Q行,每行三个数v x k,表示一组询问。v=v xor lastans,x=x xor lastans,k=k xor lastans。如果lastans=-1则不变。
Output
对于每组询问,输出一个整数表示答案。
Sample Input
10 11 4
1 2 3 4 5 6 7 8 9 10
1 4 4
2 5 3
9 8 2
7 8 10
7 1 4
6 7 1
6 4 8
2 1 5
10 8 10
3 4 7
3 4 6
1 5 2
1 5 6
1 5 8
8 9 2
Sample Output
6
1
-1
8
Hint
【数据范围】
N<=10^5, M,Q<=5*10^5,h_i,c,x<=10^9。

显然,我们题目中“边权小于等于x”的限制条件我们在Kruskal重构树上就变成了一个深度限制。
对于询问节点的祖先,如果祖先节点的权值是不超过x的,那么这颗子树中的所有节点我们都可以到达。
问题就转化成为了静态子树中的权值第k大,可以用可持久化线段树解决。
https://blog.csdn.net/hwzzyr/article/details/81190442

#include<bits/stdc++.h>
using namespace std;
const int Maxn=200005;
const int Maxm=500005;
inline int read() {
    static char c; int rec=0;
    while((c=getchar())<'0'||c>'9');
    while(c>='0'&&c<='9') rec=rec*10+c-'0',c=getchar();
    return rec;
}
int n,m,Q,N,last;
int val[Maxn<<1],table[Maxn];
int fa[Maxn<<1],st[Maxn],ed[Maxn];
inline int getfa(int x) {return x==fa[x]?x:fa[x]=getfa(fa[x]);}
struct Edge {int a,b,w;} e[Maxm];
inline bool operator < (const Edge &A,const Edge &B) {return A.w<B.w;}
namespace Sgt {
    int cnt=0,root[Maxn<<1];
    #define mid ((L+R)>>1)
    struct Dynamic_Segment_Tree {int s[2],d;} tree[Maxn*20];
    inline void Infix(int &v,int p,int L,int R,int x) {
        v=++cnt; tree[v]=tree[p]; ++tree[v].d;
        if(L==R) return ;
        int f=(x>mid); f?L=mid+1:R=mid;
        Infix(tree[v].s[f],tree[p].s[f],L,R,x);
        return ;
    }
    inline int Ask(int x,int y,int L,int R,int k) {
        if(L==R) return L;
        int sum=tree[tree[y].s[1]].d-tree[tree[x].s[1]].d;
        if(sum>=k) return Ask(tree[x].s[1],tree[y].s[1],mid+1,R,k);
        else return Ask(tree[x].s[0],tree[y].s[0],L,mid,k-sum);
    }
}
struct Branch {int next,to;} branch[Maxn<<1];
int h[Maxn<<1],cnt=0;
inline void add(int x,int y) {
    branch[++cnt].to=y; branch[cnt].next=h[x]; h[x]=cnt; return ;
}
void Ex_Kruskal() {
    int ind=n; sort(e+1,e+1+m);
    for(int i=1;i<=m;++i) {
        int fx=getfa(e[i].a),fy=getfa(e[i].b);
        if(fx!=fy) {
            fa[fx]=fa[fy]=++ind;
            val[ind]=e[i].w;
            add(ind,fx); add(ind,fy);
            if(ind==2*n-1) break;
        }
    } return ;
}
int idx=0;
int F[Maxn][18],deep[Maxn];
inline void Dfs(int v) {
    deep[v]=deep[F[v][0]]+1; st[v]=++idx;
    for(int i=1;i<18;++i)
        if(deep[v]<(1<<i)) break;
        else F[v][i]=F[F[v][i-1]][i-1];
    if(v<=n) Sgt::Infix(Sgt::root[idx],Sgt::root[idx-1],1,N,val[v]);
    else Sgt::root[idx]=Sgt::root[idx-1];
    for(int i=h[v];i;i=branch[i].next) {
        int j=branch[i].to;
        F[j][0]=v; Dfs(j);
    }
    ed[v]=idx; return ;
}
inline void Find_Pos(int &v,int lim) {
    for(int i=17;~i;--i) {
        if(deep[v]<(1<<i)) continue;
        if(val[F[v][i]]<=lim) v=F[v][i];
    } return ;
}
int main() {
    n=read(); m=read(); Q=read(); val[0]=0x3f3f3f3f;
    for(int i=1;i<=n;++i) table[i]=val[i]=read();
    sort(table+1,table+1+n); N=unique(table+1,table+1+n)-table-1;
    for(int i=1;i<=n;++i) val[i]=lower_bound(table+1,table+1+N,val[i])-table;
    for(int i=1;i<=(n<<1);++i) fa[i]=i;
    for(int i=1;i<=m;++i) {
        int a=read(),b=read(),w=read();
        e[i]=(Edge){a,b,w};
    }
    Ex_Kruskal();
    for(int i=1;i<=n;++i) if(!st[i]) Dfs(getfa(i));
    for(int i=1;i<=Q;++i) {
        int v=read()^last,x=read()^last,k=read()^last;
        Find_Pos(v,x);
        if(Sgt::tree[Sgt::root[ed[v]]].d-Sgt::tree[Sgt::root[st[v]-1]].d<k) last=-1;
        else last=table[Sgt::Ask(Sgt::root[st[v]-1],Sgt::root[ed[v]],1,N,k)];
        cout<<last<<'\n';
        last=last<0?0:last;
    }
    return 0;
}

 

posted @ 2021-04-29 09:31  我微笑不代表我快乐  阅读(58)  评论(0编辑  收藏  举报