1. 程式人生 > 其它 >CF609E:Minimum spanning tree for each edge題解

CF609E:Minimum spanning tree for each edge題解

題目連結:CF:Minimum spanning tree for each edge

題解:他要求我們求包含第i條邊的樹的最小值,我們可以先跑一遍最小生成樹,把最小生成樹求出來,然後再進行加邊刪邊處理。

當我們求出一個最小生成樹後,假如再連邊後肯定會形成一個自環,我們考慮在這個自環上刪邊(不是真刪,而是輸出減去其權值後的 ans )。

比如在該圖中,我們連線4,5兩點,這三點會與其 LCA 成頂點形成自環 ,黃色那條邊肯定是不能動的,要動的就是4-2和5-2這兩條邊,我們在處理 LCA 時變換一下,改為求解其從x點到其 LCA 的所有連邊上的最大值。我們考慮樹上倍增,定義一陣列 walth[i][j] 為從第i點開始向上跳 2^j 個點後第i點與第 2^j 點的連邊中的最大值,然後用類似於求 LCA 的做法進行處理最終求得一條鏈邊上的 max 值。

具體實現如下:

#include<iostream>
#include<algorithm> 
#include<cstdio>
#include<cmath>
#define Maxn 200005
using namespace std;
typedef long long ll;
//此部分用於樹上倍增 
ll head[Maxn*2],lg[Maxn*4];
struct acb{
    ll next,to,w;
}tree[Maxn*2];
ll cut;
void Add(ll u,ll v,ll w){
    tree[++cut].to=v;
    tree[cut].next=head[u];
    tree[cut].w=w;
    head[u]=cut;
}
//
ll anc[Maxn][30],dept[Maxn],walth[Maxn][30];
ll n,m,fa[Maxn],top,len; 
bool bin[Maxn];
struct abc{
    ll u,v,w,ty,id;
}edge[Maxn*2];
bool cmp(abc x,abc y){
    return x.w<y.w;
}
bool rbq(abc x,abc y){
    return x.id<y.id;
}
ll Find(ll x){
    while(x!=fa[x])
        x=fa[x]=fa[fa[x]];
    return x;
}
void add(ll u,ll v,ll w){
    edge[++top].v=v;
    edge[top].w=w;
    edge[top].u=u;
    edge[top].ty=top;
}
ll kruskal(){//處理出一個最小生成樹 
    sort(edge+1,edge+m+1,cmp);
    ll cnt=0,res=0;
    for(ll i=1;i<=m;i++){
        ll u=edge[i].u,v=edge[i].v;
        ll rootx=Find(u),rooty=Find(v);
        if(rootx==rooty)continue;
        bin[edge[i].ty]=true;//記錄這一條邊在生成樹裡面了,假如要加的邊也在生成樹其中,那就不用處理 
        Add(u,v,edge[i].w);//另外將最小生成樹上的每一條邊存入鏈式前向星中,以備後面的LCA 
        Add(v,u,edge[i].w);
        res+=edge[i].w;
        fa[rooty]=rootx;
//        printf("%d %d\n",u,v);
        if(++cnt==n-1)break;
    }
    return res;
}
//以上為最小生成樹部分 
void dfs(ll u,ll fath,ll w) {
    dept[u]=dept[fath]+1;
    anc[u][0]=fath;
    walth[u][0]=w;
    for(int i=1;(1<<i)<=dept[u];i++){
        anc[u][i]=anc[anc[u][i-1]][i-1];
        walth[u][i]=max(walth[u][i-1],walth[anc[u][i-1]][i-1]);//求解從u點出發直到第2^i個點之間的邊的權值的最大值 
    }
    for(int i=head[u];i;i=tree[i].next){
        if(fath!=tree[i].to){
            dfs(tree[i].to,u,tree[i].w);
        }
    }
}
ll LCA(ll x,ll y){
    if(dept[x]<dept[y]) swap(x,y);
    ll maxx=-1,maxy=-1;
    while(dept[x]>dept[y]){
        maxx=max(maxx,walth[x][lg[dept[x]-dept[y]]-1]);//萬一兩點重合了,我們就只需要一條邊上的最大值 
        //哪怕它們沒重合,我們仍然需要這一點在往上跳的途中的最大值 
        x=anc[x][lg[dept[x]-dept[y]]-1];//注:順序不要搞反了 
    }
    if(x==y) return maxx;
    for(int i=lg[dept[x]]-1;i>=0;i--){
        if(anc[x][i]!=anc[y][i]){
            maxx=max(maxx,walth[x][i]); //兩點沒有重合,那就需要我們兩邊一起求取最大值 
            maxy=max(maxy,walth[y][i]);
            x=anc[x][i];
            y=anc[y][i];
            
        }
    } 
    maxx=max(walth[x][0],maxx);//注意,我們最後求到的x值是LCA的第一級兒子,還有一條邊(LCA與其第一級兒子的連邊)沒算 
    maxy=max(walth[y][0],maxy);
    return max(maxx,maxy);
}
bool check(ll x){
    if(bin[x]) return true;
    else return false;
} 
ll work(ll x){
    ll res=len;
    if(check(x))return res;//假如這一條邊在最小生成樹中,那麼我們就不處理 
    else res+=edge[x].w; //否則,我們預先加上目標的那一條邊的權值 
    ll u=edge[x].u;
    ll v=edge[x].v;
    ll lens=LCA(u,v);
    return res-lens;
}
//以上為程式碼實踐 
int main(){
    scanf("%lld%lld",&n,&m);
    for(ll i=1;i<=n;i++)
        fa[i]=i;
    for(ll i=1;i<=m;i++){
        ll u,v,w;
        edge[i].id=i;
        scanf("%lld%lld%lld",&u,&v,&w);
        add(u,v,w);
    }
    len=kruskal();
    sort(edge+1,edge+m+1,rbq);//注:在求完最小生成樹後一定要把陣列糾正回來,因為題目要的是原裝邊加上並處理後的值 
    lg[1]=1;
    for(int i=2;i<=Maxn;i++)
        lg[i]=lg[i-1]+((1<<lg[i-1])==i);
    dfs(1,0,0);
    for(ll i=1;i<=m;i++){
        ll ans=work(i);
        printf("%lld\n",ans);
    }
    return 0;
}