Minimum spanning tree for each edge(倍增LCA)

题目链接
Connected undirected weighted graph without self-loops and multiple edges is given. Graph contains n vertices and m edges.

For each edge (u, v) find the minimal possible weight of the spanning tree that contains the edge (u, v).

The weight of the spanning tree is the sum of weights of all edges included in spanning tree.

Input

First line contains two integers n and m (1 ≤ n ≤ 2·105, n - 1 ≤ m ≤ 2·105) — the number of vertices and edges in graph.

Each of the next m lines contains three integers ui, vi, wi (1 ≤ ui, vi ≤ n, ui ≠ vi, 1 ≤ wi ≤ 109) — the endpoints of the i-th edge and its weight.

Output

Print m lines. i-th line should contain the minimal possible weight of the spanning tree that contains i-th edge.

The edges are numbered from 1 to m in order of their appearing in input.

Examples

Input

5 7
1 2 3
1 3 1
1 4 5
2 3 2
2 5 3
3 4 2
4 5 4

Output

9
8
11
8
8
8
9

题意

给出一个简单无向图,求对于每条边,包含这条边的最小生成树的权值和。

思路

初拿到此题,我就迫不及待的码了个最小生成树。
由于要求对于每条边的最小生成树,于是乎打了个循环,做了n次最小生成树。
随后,自己也掂量了一下,想了一想时间复杂度,放弃了提交。

先做一次对于全图的最小生成树,观察了一下:

  1. 若第i条边已经在这棵树当中了,我们便可以直接输出预先处理好的答案。
  2. 若第i条边不在:我们已经将一个无向图转化成了一颗树,在这棵树中没有一个环。
    对于第i条边的两个节点,我们不难发现若将其连起来,则在该树中会形成有且只有一个环。
    从两点出发,直到达到其LCA(最近公共祖先)的路径,恰好就是这个环。
    在这个环中,我们删掉除第i条边(即在那条路径中)的最大的边,即可形成新的正确的树。

于是乎,我又码了个朴素的LCA,满怀信心的交了一发(TLE)(时间复杂度n*m)。

int LCA{
    int ret=0;
    int l=x[i];
    int r=y[i];
    while(l!=r){
        if(deep[l]<deep[r]){
            ret=max(ret,TFA[r]);
            r=PFA[r];
        }
        else{
            ret=max(ret,TFA[l]);
            l=PFA[l];
        }
    }
    return ret;
}

看着这TLE的码,突然想到了倍增优化。
对于每个节点,我们原本只存一下它的父亲,慢慢遍历上去,于是造成了O(n)的复杂度。
但我们其实也可以将第i个节点的第2^j个祖先保存下来,以log(n)的复杂度找到LCA。

#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=200000;
const int MAXM=200000;
int n,m,logn;long long Min;
int fa[MAXN+5],bi[MAXM+5],dep[MAXN+5];
int lca[MAXN+5][25],lcar[MAXN+5][25];
struct node{int x,y,z,t;}s[MAXM+5];
bool cmp1(node A,node B){return A.z<B.z;}
bool cmp2(node A,node B){return A.t<B.t;}
vector<int>T[MAXN+5],P[MAXN+5];
int find(int x){
    if(x==fa[x])return x;
    return fa[x]=find(fa[x]);
}
void Add(int x,int y,int z){
    T[x].push_back(z);
    P[x].push_back(y);
}
void Init_Find(){//生成最小生成树
    sort(s+1,s+m+1,cmp1);
    for(int i=1;i<=n;i++)fa[i]=i;
    for(int i=1,j=1;i<=m&&j<n;i++){
        int a=find(s[i].x);
        int b=find(s[i].y);
        if(a!=b){
            fa[a]=b;j++;
            Min+=s[i].z;
            bi[s[i].t]=1;
            Add(s[i].x,s[i].y,s[i].z);
            Add(s[i].y,s[i].x,s[i].z);
        }
    }
    sort(s+1,s+m+1,cmp2);
}
void Init_dfs(int u,int fa){//初始化第i个节点的父亲及连接边权
    lca[u][0]=fa;
    for(int i=0;i<P[u].size();i++){
        int v=P[u][i];
        if(v==fa){
            lcar[u][0]=T[u][i];
            continue;
        }
        dep[v]=dep[u]+1;
        Init_dfs(v,u);
    }
}
void Init_LCA(){//初始化完第i个节点的第2^j个祖先,及沿途最大边权
    for(int i=1;(1<<i)<=n;i++)
        for(int j=1;j<=n;j++)
            if(lca[j][i-1]){
                lca[j][i]=lca[lca[j][i-1]][i-1];
                lcar[j][i]=lcar[lca[j][i-1]][i-1];
                lcar[j][i]=max(lcar[j][i],lcar[j][i-1]);
            }
}
int LCA_RET(int x,int y){//查找x,y节点至其LCA路径上的最大边权
    int ret=0;
    if(dep[x]<dep[y])//固定x比y深
        swap(x,y);
    int de=dep[x]-dep[y];
    for(int i=logn;i>=0;i--)//二进制上升,使x,y同一高度
        if(de>>i&1){
            ret=max(ret,lcar[x][i]);
            x=lca[x][i];
        }
    //printf("%d %d ",dep[x],dep[y]);
    if(x==y)return ret;//若y为LCA(即x,y在同一条链上)
        for(int i=logn;i>=0;i--)//不断上升同一高度
        if(lca[x][i]!=lca[y][i]){
            ret=max(ret,max(lcar[x][i],lcar[y][i]));
            x=lca[x][i];y=lca[y][i];
        }
    ret=max(ret,max(lcar[x][0],lcar[y][0]));
    return ret;
}
int main(){
    //freopen("span.in","r",stdin);
    //freopen("span.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d",&s[i].x);
        scanf("%d",&s[i].y);
        scanf("%d",&s[i].z);
        s[i].t=i;
    }
    logn=20;
    Init_Find();
    Init_dfs(1,0);
    Init_LCA();
    for(int i=1;i<=m;i++){
        if(bi[i])printf("%lld\n",Min);
        else{
            int tmp=LCA_RET(s[i].x,s[i].y);
            printf("%lld\n",Min-tmp+s[i].z);
        }
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值