Loading

浅谈Kruskal重构树

如何构建

当运行 \(Kruskal\)​ 算法找到当前一条可加入的边时,设左右端点在并查集中的所属位置和边权分别为 \(x,y,val\)​,在新图上新建一个节点 \(T\)​,并加入 \((T,x),(T,y)\)​ 两条边(可以直接加有向边,因为新图中的树的根和方向都固定了),并把 \(T\)​ 的点权赋为 \(val\)​,将并查集中 \(x,y\)​ 合并到 \(T\)​ 中,即为完成了一次加点构建。在新加了 \(n-1\)​ 个点后,新图中的就是一棵 \(Kruskal\)​ 重构树了(如果原图不连通,则新图为 \(Kruskal\)​ 重构树森林),其根节点为最后新建的新点(若是森林,则为并查集中所属位置等于自己的点)。

void kruskal(){
    sort(e+1,e+1+m,[](edge x,edge y){return x.val>y.val;});
    for(int i=1;i<=n;++i) ff[i]=i;
    for(int i=1;i<=m;++i){
        int x=find(e[i].from),y=find(e[i].to);
        if(x!=y){
            val[++cnt]=e[i].val;
            ff[x]=ff[y]=ff[cnt]=cnt;
            adde(cnt,x),adde(cnt,y);
        }
    }
}

一些手滑点:\(cnt\) 初值要赋为 \(n\),并查集要赋初值(老问题了)。

一些性质

\(Kruskal\) 重构树拥有一些奇妙的性质:(下文简称为“重构树”)

  • 原图中的两点之间的路径中最小的最大边权为这两点在重构树上的 \(lca\) 的点权(这是在原图升序排序下的性质,降序排序则为最大的最小边权)(核心性质)

  • 原图中的点在重构树中均是叶子结点

  • 重构树是一个二叉树

  • 重构树在原图边权升序排序下是一个大根堆,降序排序下是一个小根堆(由此性质可证得第一个性质)

一些运用

LuoguP1967 [NOIP2013 提高组] 货车运输

没学 \(Kruskal\) 重构树之前,这是一道神仙的树上倍增题(要在原图的最大生成树上跑),学了之后这就是一道简简单单的板子题了。

注意:这是 \(Kruskal\) 重构树森林,对所有的树根都要dfs预处理(数据太水导致我没有全部处理都过了)

当然还有更板子的题:BZOJ3732

#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
    T r=0,f=0;char c=getchar();
    while(!isdigit(c)) f|=c=='-',c=getchar();
    while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
    return f?-r:r;
}
const int N=5e4+5;
int n,m;
struct edge{int from,to,val;}e[N];
int ff[N];
int hd[N],nx[N],to[N],tote,val[N],cnt;
int dep[N],top[N],sz[N],son[N],fa[N];
void adde(int u,int v){
    nx[++tote]=hd[u];to[tote]=v;hd[u]=tote;
    nx[++tote]=hd[v];to[tote]=u;hd[v]=tote;
}
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
void dfs1(int u,int father){
    dep[u]=dep[father]+1;fa[u]=father;sz[u]=1;
    for(int i=hd[u];i;i=nx[i]){
        int v=to[i];
        if(v==father) continue;
        dfs1(v,u);
        sz[u]+=sz[v];
        if(sz[v]>sz[son[u]]) son[u]=v;
    }
}
void dfs2(int u,int anc){
    top[u]=anc;
    if(son[u]) dfs2(son[u],anc);
    for(int i=hd[u];i;i=nx[i]){
        int v=to[i];
        if(v==fa[u]||v==son[u]) continue;
        dfs2(v,v);
    }
}
int LCA(int x,int y){
    while(top[x]!=top[y]){
        if(dep[top[x]]<dep[top[y]]) swap(x,y);
        x=fa[top[x]];
    }
    if(dep[x]>dep[y]) swap(x,y);
    return x;
}
void kruskal(){
    sort(e+1,e+1+m,[](edge x,edge y){return x.val>y.val;});
    for(int i=1;i<=n;++i) ff[i]=i;
    for(int i=1;i<=m;++i){
        int x=find(e[i].from),y=find(e[i].to);
        if(x!=y){
            val[++cnt]=e[i].val;
            ff[x]=ff[y]=ff[cnt]=cnt;
            adde(cnt,x),adde(cnt,y);
        }
    }
    for(int i=1;i<=n*2-1;++i) if(ff[i]==i) dfs1(i,0),dfs2(i,i);
}
int main(){
    cnt=n=read<int>(),m=read<int>();
    for(int i=1;i<=m;++i){
        int x=read<int>(),y=read<int>(),z=read<int>();
        e[i]=(edge){x,y,z};
    }
    kruskal();
    for(int Q=read<int>();Q;--Q){
        int x=read<int>(),y=read<int>();
        if(find(x)!=find(y)) puts("-1");
        else printf("%d\n",val[LCA(x,y)]);
    }
    return 0;
}

LuoguP4197 Peaks

这题直接上在线做法,也可以过P7834 [ONTAK2010] Peaks 加强版

先构建出重构树,可以发现从 \(v\)​ 开始走边权不大于 \(x\)​ 的边能走到的区间,等价于在重构树上从 \(v\)​ 开始向上跳,每次只能走点权小于等于 \(x\)​ 的点,最后停在的那个结点的所有叶子结点即为能原图中走到的所有点。

把叶子结点按dfs序编号,考虑dfs一遍给每个结点都算出它的叶子结点区间(我这里用的是左开右闭的区间),\(x\)​ 向上跳的过程可以用倍增实现,找出区间后用主席树查找区间第 \(k\)即可。

#include<bits/stdc++.h>
#define id(u) lower_bound(b+1,b+1+len,a[u])-b
using namespace std;
template <class T>
inline T read(){
    T r=0,f=0;char c=getchar();
    while(!isdigit(c)) f|=c=='-',c=getchar();
    while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
    return f?-r:r;
}
const int N=5e5+5;
int n,m,Q,ans;
struct edge{int from,to,val;}e[N];
int a[N],b[N],len;
int ff[N];
int f[N][25],L[N],R[N],num;
int hd[N],nx[N],to[N],val[N],tote,cnt;
int rt[N],sz[N*20],ls[N*20],rs[N*20],tot;
void adde(int u,int v){nx[++tote]=hd[u];to[tote]=v;hd[u]=tote;}
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
void insert(int &y,int x,int l,int r,int p){
    y=++tot,ls[y]=ls[x],rs[y]=rs[x],sz[y]=sz[x]+1;
    if(l==r) return;
    int mid=l+r>>1;
    if(p<=mid) insert(ls[y],ls[x],l,mid,p);
    else insert(rs[y],rs[x],mid+1,r,p);
}
int query(int y,int x,int l,int r,int k){
    if(l==r) return l;
    int mid=l+r>>1,d=sz[rs[y]]-sz[rs[x]];
    if(k<=d) return query(rs[y],rs[x],mid+1,r,k);
    else return query(ls[y],ls[x],l,mid,k-d);
}
void dfs(int u,int father){
    f[u][0]=father;L[u]=num;
    for(int i=1;i<=20;++i) f[u][i]=f[f[u][i-1]][i-1];
    if(!hd[u]) ++num,insert(rt[num],rt[num-1],1,len,id(u));
    for(int i=hd[u];i;i=nx[i]){
        int v=to[i];
        if(v==father) continue;
        dfs(v,u);
    }
    R[u]=num;
}
void kruskal(){
    for(int i=1;i<=n;++i) ff[i]=i;
    sort(e+1,e+1+m,[](edge x,edge y){return x.val<y.val;});
    for(int i=1;i<=m;++i){
        int x=find(e[i].from),y=find(e[i].to);
        if(x!=y){
            val[++cnt]=e[i].val;
            ff[cnt]=ff[x]=ff[y]=cnt;
            adde(cnt,x),adde(cnt,y);
        }
    }
    dfs(cnt,0);
}
void query(int x,int c,int k){
    for(int i=20;i>=0;--i) if(f[x][i]&&val[f[x][i]]<=c) x=f[x][i];
    if(R[x]-L[x]<k) ans=0,puts("-1");
    else printf("%lld\n",ans=b[query(rt[R[x]],rt[L[x]],1,len,k)]);
}
signed main(){
    cnt=n=read<int>(),m=read<int>(),Q=read<int>();
    for(int i=1;i<=n;++i) b[i]=a[i]=read<int>();
    sort(b+1,b+1+n);
    len=unique(b+1,b+1+n)-b-1;
    for(int i=1;i<=m;++i){
        int x=read<int>(),y=read<int>(),z=read<int>();
        e[i]=(edge){x,y,z};
    }
    kruskal();
    while(Q--){
        int x=(read<int>()^ans)%n+1,y=read<int>()^ans,z=(read<int>()^ans)%n+1;
        query(x,y,z);
    }
    return 0;
}

LuoguP4768 [NOI2018] 归程

知道了重构树也还是很好想的。

用重构树和树上倍增求出所有能走到的点,从 \(1\)​ 跑一遍最短路,然后在当前点能走到的点中取到 \(1\)​ 距离最小的就好了。可以先处理出来重构树上的点,子树内的所有叶子结点的距离最小值,就可以实现快速查询。

多测不清零,调码两行泪。

#include<bits/stdc++.h>
using namespace std;
template <class T>
inline T read(){
    T r=0,f=0;char c=getchar();
    while(!isdigit(c)) f|=c=='-',c=getchar();
    while(isdigit(c)) r=(r<<3)+(r<<1)+(c^48),c=getchar();
    return f?-r:r;
}
const int N=4e5+5,INF=1e9;
struct edge{int from,to,val;}e[N];
struct zfz{
    int u,val;
    bool operator <(const zfz x)const{return x.val<val;}
};
int n,m,ans;
int dis[N];
bool vis[N];
int ff[N],val[N],cnt;
int hd[N],to[N],nx[N],tote;
int hd2[N],to2[N<<1],nx2[N<<1],val2[N<<1],tote2;
int fa[N][25],Min[N];
void adde(int u,int v){nx[++tote]=hd[u];to[tote]=v;hd[u]=tote;}
void adde2(int u,int v,int c){
    nx2[++tote2]=hd2[u];to2[tote2]=v;hd2[u]=tote2;val2[tote2]=c;
    nx2[++tote2]=hd2[v];to2[tote2]=u;hd2[v]=tote2;val2[tote2]=c;
}
int find(int x){return ff[x]==x?x:ff[x]=find(ff[x]);}
void dij(){
    priority_queue<zfz> q;
    memset(dis,0x7f,sizeof(dis));
    memset(vis,0,sizeof(vis));
    q.push((zfz){1,0});dis[1]=0;
    while(!q.empty()){
        int u=q.top().u;q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(int i=hd2[u];i;i=nx2[i]){
            int v=to2[i];
            if(dis[v]>dis[u]+val2[i]){
                dis[v]=dis[u]+val2[i];
                if(!vis[v]) q.push((zfz){v,dis[v]});
            }
        }
    }
}
void dfs(int u,int father){
    fa[u][0]=father;
    for(int i=1;i<=20;++i) fa[u][i]=fa[fa[u][i-1]][i-1];
    if(!hd[u]) Min[u]=dis[u];
    for(int i=hd[u];i;i=nx[i]){
        int v=to[i];
        dfs(v,u);
        Min[u]=min(Min[u],Min[v]);
    }
}
void kruskal(){
    sort(e+1,e+1+m,[](edge x,edge y){return x.val>y.val;});
    for(int i=1;i<=n;++i) ff[i]=i;
    for(int i=1;i<=m;++i){
        int x=find(e[i].from),y=find(e[i].to);
        if(x!=y){
            val[++cnt]=e[i].val;
            ff[x]=ff[y]=ff[cnt]=cnt;
            adde(cnt,x),adde(cnt,y);
        }
    }
    dfs(cnt,0);
}
void query(int x,int c){
    for(int i=20;i>=0;--i) if(fa[x][i]&&val[fa[x][i]]>c) x=fa[x][i];
    printf("%d\n",ans=Min[x]);
}
void init(){
    memset(fa,0,sizeof(fa));
    memset(hd,0,sizeof(hd));
    memset(hd2,0,sizeof(hd2));
    memset(Min,0x7f,sizeof(Min));
    tote=tote2=ans=0;
}
int main(){
    for(int T=read<int>();T;--T){
        init();
        cnt=n=read<int>(),m=read<int>();
        for(int i=1;i<=m;++i){
            int x=read<int>(),y=read<int>(),z=read<int>(),a=read<int>();
            e[i]=(edge){x,y,a};adde2(x,y,z);
        }
        dij();
        kruskal();
        for(int Q=read<int>(),k=read<int>(),s=read<int>();Q;--Q){
            int x=(read<int>()+k*ans-1)%n+1,y=(read<int>()+k*ans)%(s+1);
            query(x,y);
        }
    }
    return 0;
}

5个推荐暴切狼人

posted @ 2021-09-16 12:14  Quick_Kk  阅读(108)  评论(0)    收藏  举报