Codeforces 609E (Kruskal求最小生成树+树上倍增求LCA)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/oier_forever/article/details/80961784

题面

传送门
题目大意:
给定一个无向连通带权图G,对于每条边 ( u , v , w ) ,求包含这条边的生成树大小的最小值

分析

包含这条边的生成树的大小如何表示呢?
先求出整张图的最小生成树大小tlen,对于每一条边 ( u , v , w ) ,我们最小生成树中去掉树上从u到v的路径上权值最大,最大值为mlen的一条边,再加上w,得到的一定是包含这条边的生成树大小的最小值 t l e n m l e n + w

最小生成树大小tlen可用kruskal算法在 O ( m l o g 2 m ) 时间内求出
那么问题转化为求mlen,可用树上倍增法求解

树上倍增法的好处是在求LCA的同时可以维护更多的附加信息
在求LCA的过程中设fa[i][j]表示i的 2 j 辈祖先
可写出公式

f a [ i ] [ j ] = f a [ f a [ i ] [ j 1 ] ] [ j 1 ]

(即i的 2 j 辈祖先是i的 2 j 1 辈祖先的 2 j 1 辈祖先)
同理可写出最大长度
m l e n [ i ] [ j ] = m a x ( m l e n [ i ] [ j 1 ] , m l e n [ m l e n [ i ] [ j 1 ] ] [ j 1 ] )

查询时类似LCA的查询即可,详情见代码

代码

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<cmath>
#define maxn 200005
#define maxm 200005
#define maxlog 32
using namespace std;
int n,m;
inline int qread(){
    int x=0,sign=1;
    char c=getchar();
    while(c<'0'||c>'9'){
        if(c=='-') sign=-1;
        c=getchar();
    }
    while(c>='0'&&c<='9'){
        x=x*10+c-'0';
        c=getchar();
    }
    return x*sign;
}
struct edge{
    int from;
    int to;
    int len;
    int next;
    int index;
    edge(){

    }
    edge(int x,int y,int z,int i){
        from=x;
        to=y;
        len=z;
        index=i;
    }
    friend bool operator <(edge x,edge y){
        return x.len<y.len;
    }
};
edge G[maxm*2],MST[maxm*2];
int head[maxn];
int size=0;
void add_edge(int u,int v,int w){
    size++;
    MST[size].from=u;
    MST[size].to=v;
    MST[size].len=w;
    MST[size].next=head[u];
    head[u]=size;
}
int fset[maxn];
void set_init(){
    for(int i=1;i<=n;i++) fset[i]=i;
}
int find(int x){
    if(fset[x]==x) return x;
    else{
        fset[x]=find(fset[x]);
        return fset[x];
    }
}
long long kruskal(){
    long long ans=0;
    sort(G+1,G+1+m);
    for(int i=1;i<=m;i++){
        int fx=find(G[i].from);
        int fy=find(G[i].to);
        if(fx!=fy){
            add_edge(G[i].from,G[i].to,G[i].len);
            add_edge(G[i].to,G[i].from,G[i].len);
            fset[fx]=fy;
            ans+=G[i].len; 
        }
    }
    return ans;
}

int deep[maxn],fa[maxn][maxlog];
long long mlen[maxn][maxlog];
int log2n;
void lca_init(){
    queue<int>q;
    q.push(1);
    deep[1]=1; //初始化深度
    while(!q.empty()){
        int x=q.front();
        q.pop();
        for(int i=head[x];i;i=MST[i].next){//MST表示最小生成树的边
            int y=MST[i].to;
            if(deep[y]) continue;
            deep[y]=deep[x]+1;
            fa[y][0]=x;//fa和mlen的初始值
            mlen[y][0]=MST[i].len;
            for(int j=1;j<=log2n;j++){//倍增初始化
                fa[y][j]=fa[fa[y][j-1]][j-1];
                mlen[y][j]=max(mlen[y][j-1],mlen[fa[y][j-1]][j-1]);
            }
            q.push(y);
        }
    }
}
long long lca_query(int x,int y){
    if(deep[x]>deep[y]) swap(x,y);
    long long maxl=0;
    for(int i=log2n;i>=0;i--){//先将x和y调整到同一深度
        if(deep[fa[y][i]]>=deep[x]){
            maxl=max(maxl,mlen[y][i]);//y上升同时更新maxl
            y=fa[y][i];
        }
    }
    if(x==y) return maxl;//如果LCA(x,y)=x,直接返回
    for(int i=log2n;i>=0;i--){//x,y同时上升,直到差一条边相遇
        if(fa[x][i]!=fa[y][i]){
            maxl=max(maxl,max(mlen[x][i],mlen[y][i]));
            x=fa[x][i];
            y=fa[y][i];
        }
    }
    maxl=max(maxl,max(mlen[x][0],mlen[y][0]));//最后再更新一次
    return maxl;
}
long long ans[maxm];//便于按输入顺序输出
int main(){
    int s,t,r;
    n=qread();
    m=qread();
    for(int i=1;i<=m;i++){
        s=qread();
        t=qread();
        r=qread();
        G[i]=edge(s,t,r,i);
    }
    set_init();
    long long tlen=kruskal();
    log2n=log2(n)+1;
    lca_init();
    for(int i=1;i<=m;i++){
        ans[G[i].index]=tlen+(long long)G[i].len-(long long)lca_query(G[i].from,G[i].to);//求生成树大小的最小值
    }
    for(int i=1;i<=m;i++){
        printf("%I64d\n",ans[i]);
    }
}

猜你喜欢

转载自blog.csdn.net/oier_forever/article/details/80961784
今日推荐