kruskal 重构树专题

2023.11.13 14:46

这是基于 kruskal 求最小生成树的算法将无向带边权图转化成一种有特殊性质的有 \(2n+1\) 个节点的带点权树。

  • 如果对原图跑最小生成树的重构,则两点之间最大边的最小值为其在重构树上LCA的权值。
  • 如果对原图跑最大生成树的重构,则两点之间最小边的最大值为其在重构树上LCA的权值。

有这个优秀的性质,我们就可以做一些奇怪的题目了。

U92652 【模板】kruskal重构树

直接看代码吧,这里用的是 dfn 求 lca,注意一下这题是森林,得对每个联通块跑一棵树。

\(Code\)

const int N=3e5+5;
struct edge{
    int x,y,w;
    inline bool operator <(const edge &o)const{return w<o.w;}
}e[N];
int n,m,q,belong[N],fa[23][N<<1],cnt,w[N<<1],dfn[N<<1],dn,lg[N<<1];
int find(int x){
    if(x==belong[x])return x;
    return belong[x]=find(belong[x]);
}
vector<int>f[N<<1];
void dfs(int x,int FA){
    fa[0][dfn[x]=++dn]=FA;
    for(int y:f[x])dfs(y,x);
}
inline int MIN(int x,int y){return dfn[x]<dfn[y]?x:y;}
inline int LCA(int x,int y){
    if(x==y)return x;
    if((x=dfn[x])>(y=dfn[y]))swap(x,y);
    int k=lg[y-x++];
    return MIN(fa[k][x],fa[k][y-(1<<k)+1]);
}
int main(){
    cnt=n=read(),m=read(),q=read();
    for(int i=1;i<(n<<1);i++)belong[i]=i,lg[i+1]=lg[i+1>>1]+1;
    for(int i=1;i<=m;i++)
        e[i]={read(),read(),read()};
    sort(e+1,e+1+m);
    for(int i=1;i<=m;i++){
        int x=e[i].x,y=e[i].y;
        if((x=find(x))==(y=find(y)))continue;
        belong[x]=belong[y]=++cnt;
        f[cnt].push_back(x);
        f[cnt].push_back(y);
        w[cnt]=e[i].w;
    }
    for(int i=1;i<=cnt;i++)
        if(belong[i]==i)dfs(i,0);
    for(int k=1;k<=lg[cnt];k++)
        for(int i=1;i+(1<<k)-1<=cnt;i++)
            fa[k][i]=MIN(fa[k-1][i],fa[k-1][i+(1<<k-1)]);
    while(q--){
        int x=read(),y=read(),lca=LCA(x,y);
        printf("%d\n",w[lca]?w[lca]:-1);
    }
    return 0;
}

P1967 [NOIP2013 提高组] 货车运输

准确来说这道题应该是最大生成树建重构的模板,把上面代码改改就过了。

为了一次性水两篇题解,我在这里稍微解释一下。

kruskal 重构树

基于 kruskal 求最小生成树的算法将无向带边权图转化成一种有特殊性质的有 \(2n+1\) 个节点的带点权树。

  • 如果对原图跑最小生成树的重构,则两点之间最大边的最小值为其在重构树上 LCA 的权值。
  • 如果对原图跑最大生成树的重构,则两点之间最小边的最大值为其在重构树上 LCA 的权值。

有这个优秀的性质,我们就可以做这道题目了。

我们关注到,这道题的本质就是让我们求两个点 \(x,y\) 之间的最小值的最大值,所以我们只需要对这个图做一个 kruskal 最小重构树,然后在树上求 LCA 求其点权即可。这里我用的是 dfn 求 LCA,有兴趣的可以自己去学一下,这个还是很有用的,单次查询 \(O(1)\),预处理 \(O(n\log n)\)

Code

#include<bits/stdc++.h>
using namespace std;
const int N=3e5+5;
int read(){
    int x=0;char c=getchar();
    for(;!isdigit(c);c=getchar());
    for(;isdigit(c);c=getchar())x=(x<<3)+(x<<1)+(c^48);
    return x;
}
struct edge{
    int x,y,w;
    inline bool operator <(const edge &o)const{
        return w>o.w;
    }
}e[N];
int n,m,q,belong[N],fa[23][N<<1],cnt,w[N<<1],dfn[N<<1],dn,lg[N<<1];
int find(int x){
    if(x==belong[x])return x;
    return belong[x]=find(belong[x]);
}
vector<int>f[N<<1];
void dfs(int x,int FA){
    fa[0][dfn[x]=++dn]=FA;
    for(int y:f[x])dfs(y,x);
}
inline int MIN(int x,int y){return dfn[x]<dfn[y]?x:y;}
inline int LCA(int x,int y){
    if(x==y)return x;
    if((x=dfn[x])>(y=dfn[y]))swap(x,y);
    int k=lg[y-x++];
    return MIN(fa[k][x],fa[k][y-(1<<k)+1]);
}
int main(){
    while(cin>>n>>m>>q){
        cnt=n,dn=0;
        for(int i=1;i<(n<<1);i++)belong[i]=i,lg[i+1]=lg[i+1>>1]+1,f[i].clear();
        for(int i=1;i<=m;i++)
            e[i]={read(),read(),read()};
        sort(e+1,e+1+m);
        for(int i=1;i<=m;i++){
            int x=e[i].x,y=e[i].y;
            if((x=find(x))==(y=find(y)))continue;
            belong[x]=belong[y]=++cnt;
            f[cnt].push_back(x);
            f[cnt].push_back(y);
            w[cnt]=e[i].w;
        }
        dfs(cnt,0);
        for(int k=1;k<=lg[cnt];k++)
            for(int i=1;i+(1<<k)-1<=cnt;i++)
                fa[k][i]=MIN(fa[k-1][i],fa[k-1][i+(1<<k-1)]);
        for(;q--;){
            int x=read(),y=read();
            printf("%d\n",find(x)==find(y)?w[LCA(x,y)]:-1);
        }
    }
    return 0;
}

P4768 [NOI2018] 归程

终于到归程了,久仰大名,今天看看能不能用 kruskal 重构树写掉。

太妙了太妙了。通过 1506 机房巨巨巨巨巨佬 xxx2022 的一点点提示,我已经大概想到这题怎么写了。(梵神!!!!!)

首先我们先建一棵最大重构树找最短边的最大值,如果这个最大值比这个水位线要大那显然我们的小 jio 就不用碰地了;那如果没有呢?我们也不一定是在最小值哪里停,那怎么办?

我们发现我们从 \(1\) 出发到 \(v\) 和从 \(v\)\(1\) 是可以通过某种方式转化的。为什么要转化?因为我们求最短路肯定是从 \(1\) 开始跑 dijkstra 啊,而且从 \(v\)\(1\) 的一部分距离是不需要算距离的,而从 \(1\) 开始是前一段算距离而后不算。

我们从 \(v\) 开始在重构树上倍增找最后一个点权大于水位线的点,因为 \(v\) 可以无代价到它的子树的所有点,我们必须在这些点里面选一个停车,那么我们只需要求这子树里面到 \(1\) 距离最小的点就行。

而根据子树的性质我们可以再用倍增维护一个连续段的 dis 最小值,然后就做完了~

18:22
微调一遍过!!!!(除了一开始写成 F[u].push_back({w,u}) 其他一点问题都没有!!!)。

\(Code\)

typedef long long ll;
typedef pair<ll,int> PII;
const int N=2e5+5;
struct edge{
    int x,y,h;
    inline bool operator<(const edge&w)const{return h>w.h;}
}e[N<<1];
int n,m,belong[N<<1],lg[N<<1],tot;
int fa[25][N<<1],H[N<<1];
int siz[N<<1],dfn[N<<1],dn,dep[N<<1];//siz is the number of leaves in the subtree
//dfn存最小重构dfs序id(只有叶节点有)
vector<PII>F[N];//for the shortest path
vector<int>g[N<<1];//for counting on the rebuilt tree
ll dis[N],dist[25][N];
bool vis[N];
int find(int x){return (x==belong[x]?x:belong[x]=find(belong[x]));}
void resilence(){
    tot=n,dn=0;
    memset(dep,0,sizeof dep);
    memset(fa,0,sizeof fa);
    for(int i=1;i<(n<<1);i++){
        belong[i]=i,dfn[i]=1e9,siz[i]=0,g[i].clear();
    }
    for(int i=1;i<=n;i++){
        F[i].clear();
        vis[i]=0;
        dis[i]=1e18;
    }
    return;
}
void dfs(int x){
    dep[x]=dep[fa[0][x]]+1;
    if(x<=n)return dfn[x]=++dn,siz[x]=1,void();
    for(int y:g[x])
        dfs(y),siz[x]+=siz[y],dfn[x]=min(dfn[x],dfn[y]);
    return;
}
int LCA(int x,int y){
    if(dep[x]>dep[y])swap(x,y);
    for(int k=lg[dep[y]-dep[x]];~k;--k)
        if(dep[fa[k][x]]<=dep[y])x=fa[k][x];
    if(x==y)return x;
    for(int k=lg[dep[x]];~k;--k)
        if(fa[k][x]!=fa[k][y])x=fa[k][x],y=fa[k][y];
    return fa[0][x];
}
void Dijkstra(){
    priority_queue<PII>Q;
    Q.push({dis[1]=0,1});
    while(Q.size()){
        int x=Q.top().second;Q.pop();
        if(vis[x])continue;
        vis[x]=1;
        for(PII PI:F[x]){
            int y=PI.second,w=PI.first;
            if(dis[y]>dis[x]+w)
                Q.push({-(dis[y]=dis[x]+w),y});
        }
    }
    for(int i=1;i<=n;i++)dist[0][dfn[i]]=dis[i];
    for(int k=1;k<=lg[n];k++)
        for(int i=1;i+(1<<k)-1<=n;i++)
            dist[k][i]=min(dist[k-1][i],dist[k-1][i+(1<<k-1)]);
    return;
}
int main(){
    for(int i=2;i<=(N<<1)-9;i++)lg[i]=lg[i>>1]+1;
    for(int T=read();T--;){
        n=read(),m=read();
        resilence();
        for(int i=1;i<=m;i++){
            int u=read(),v=read(),w=read(),h=read();
            F[u].push_back({w,v});
            F[v].push_back({w,u});
            e[i]={u,v,h};
        }
        sort(e+1,e+1+m);
        for(int i=1;i<=m;i++){
            int x=find(e[i].x),y=find(e[i].y);
            if(x==y)continue;
            fa[0][x]=fa[0][y]=belong[x]=belong[y]=++tot;
            g[tot].push_back(x);
            g[tot].push_back(y);
            H[tot]=e[i].h;
        }
        for(int k=1;k<=lg[tot];++k)
            for(int i=1;i<=tot;i++)
                fa[k][i]=fa[k-1][fa[k-1][i]];
        dfs(tot);
        Dijkstra();
        for(ll q=read(),K=read(),s=read(),lasans=0;q--;){
            int v=(read()+K*(lasans%n)%n-1+n)%n+1,p=(read()+K*(lasans%(s+1)))%(s+1);
            if(H[LCA(1,v)]>p)printf("%lld\n",lasans=0);
            else{
                int u=v;
                for(int k=lg[dep[v]];~k;--k)
                    if(H[fa[k][u]]>p)u=fa[k][u];
                int k=lg[siz[u]];
                printf("%lld\n",lasans=min(dist[k][dfn[u]],dist[k][dfn[u]+siz[u]-(1<<k)]));
            }
        }
    }
    return 0;
}

P4185 [USACO18JAN] MooTube G

挺显然的,就是求最大重构树求最小值的最大值,然后对每个点倍增找刚好大于等于 \(K\) 的点求其子树大小即可。

posted @ 2023-11-13 19:56  NBest  阅读(48)  评论(0)    收藏  举报