题解:P14080 [GESP202509 八级] 最小生成树

· · 题解

这是 GESP???

在机房走之前看到了这个题,路上思考 10min 无果,回家手玩样例发现我糖丸了。

首先求出最小生成树,之后我们发现,每删一条边就是将原树分为两个不同的联通快,你需要在这两个联通块的其余点对间找一条最短非树边加上。

发现正着做不好维护,考虑反过来。

我们发现,对于一条非树边,它会对该边两个端点之间的树上路径产生贡献,即若删除这两个点树上路径的任意一边,都可以用该边替代,证明显然。

所以将每一条非树边的权值丢进线段树跑树剖,之后对于每一条树边,求出路径上最小值,一加一减即为答案。

注意这里是边权,所以要将边权转成点权,把每条边的权值下放到他的靠下的端点即可,详情见 P4315。

代码如下:


#include<bits/stdc++.h>
#define int long long
using namespace std;
struct node{
    int l,r,add,mi;
    #define l(x) tree[x].l
    #define r(x) tree[x].r
    #define add(x) tree[x].add
    #define mi(x) tree[x].mi
}tree[1000005];
int res,n,m,x,y,k,a[1000005],root,MOD,tim,w[1000005],fat[1000005],cnt,val[1000005];
bool flag[1000005];
int dep[1000005];//深度
int id[1000005];//所在重链编号
int fa[1000005];//父节点编号
int siz[1000005];//子树大小
int heavy[1000005];//重儿子编号
int dfn[1000005];//dfs序
vector<int>vec[1000005];
void dfs(int p,int d,int fat)
{
    fa[p]=fat,dep[p]=d,siz[p]=1;
    for(auto it:vec[p])
    {
        if(it==fat) continue;
        dfs(it,d+1,p);
        siz[p]+=siz[it];
        if(siz[heavy[p]]<siz[it]) heavy[p]=it;
    }
}
void dfs1(int p,int start,int fat)
{
    dfn[p]=(++tim),id[p]=start,w[tim]=a[p];
    if(!heavy[p]) return ;
    dfs1(heavy[p],start,p);
    for(auto it:vec[p])
    {
        if(it==heavy[p]||it==fat) continue;  
        dfs1(it,it,p);
    }
}
void build(int p,int l,int r)
{
    l(p)=l,r(p)=r,mi(p)=add(p)=9e18;
    if(l==r) return ;
    int mid=l+r>>1;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}
void spread(int p)
{
    if(add(p)!=9e18)
    {
        add(p*2)=min(add(p*2),add(p));
        add(p*2+1)=min(add(p*2+1),add(p));
        mi(p*2)=min(mi(p*2),add(p));
        mi(p*2+1)=min(mi(p*2+1),add(p));
        add(p)=9e18;
    }
} 
void change(int p,int l,int r,int d)
{
    if(l<=l(p)&&r(p)<=r) 
    {
        mi(p)=min(mi(p),d);
        add(p)=min(add(p),d);
        return ;
    }
    spread(p);
    int mid=(l(p)+r(p))>>1;
    if(l<=mid) change(p*2,l,r,d);
    if(r>mid) change(p*2+1,l,r,d);
    mi(p)=min(mi(p*2),mi(p*2+1));
}
int ask(int p,int l,int r)
{
    if(l<=l(p)&&r(p)<=r) return mi(p);
    spread(p);
    int mid=(l(p)+r(p))>>1;
    int ans=9e18;
    if(l<=mid) ans=min(ans,ask(p*2,l,r));
    if(mid<r) ans=min(ans,ask(p*2+1,l,r));
    return ans;
}
void tree_change(int l,int r,int k)
{
    //钦定l的重链的起始点深度更大 
    while(id[l]!=id[r])
    {
        if(dep[id[l]]<dep[id[r]]) swap(l,r);
        change(1,dfn[id[l]],dfn[l],k);
        l=fa[id[l]];
    }
    if(dep[l]<dep[r]) swap(l,r);
    change(1,dfn[r]+1,dfn[l],k);
}
struct node1{
    int to,next,w,id;
}t[1000005]; 
bool cmp(node1 a,node1 b)
{
    return a.w<b.w;
}
int find(int i)
{
    if(i==fat[i]) return i;
    return fat[i]=find(fat[i]);
}
int kruscal()
{
    int ans=0,cnt1=0;
    for(int i=1;i<=cnt;i++)
    {
        int x=find(t[i].to),y=find(t[i].next);
        if(x!=y)
        {
            flag[i]=1;
            fat[x]=y;
            vec[t[i].to].push_back(t[i].next);
            vec[t[i].next].push_back(t[i].to);
            ans+=t[i].w;
            cnt1++;
        }   
    }
    if(cnt1==n-1) return ans;
    return -1;
}
signed main()
{
    cin>>n>>m;
    for(int i=1;i<=n;i++) fat[i]=i;
    for(int i=1;i<=m;i++) 
    {
        int u,v,w;
        cin>>u>>v>>w;
        t[++cnt]={u,v,w,i};
    }
    sort(t+1,t+cnt+1,cmp);
    res=kruscal();
    dfs(1,1,0);dfs1(1,1,0);
    build(1,1,n);
    for(int i=1;i<=m;i++)
    {
        int pos=t[i].id;
        if(flag[i]) continue;
        tree_change(t[i].to,t[i].next,t[i].w);
    }
    for(int i=1;i<=m;i++)
    {
        int pos=t[i].id;
        if(!flag[i]){val[pos]=res;continue;}
        int x;
        if(dep[t[i].to]>dep[t[i].next]) x=t[i].to;
        else x=t[i].next;
        int mi=ask(1,dfn[x],dfn[x]);
        if(mi==9e18){val[pos]=-1;continue;};
        val[pos]=res+mi-t[i].w;
    }
    for(int i=1;i<=m;i++) cout<<val[i]<<"\n";
    return 0;
}