次小生成树(学习笔记)

P4180 BJWC2010 严格次小生成树 - 洛谷

简介:

如同字面意思。次小生成树就是严格只比最小生成树大一点的生成树。数学公式来表达就是:

若最小生成树选出的边集为 \(E_M\),严格次小生成树的边集为 \(E_S\),若 \(value(e)\) 表示边 \(e\) 的权值。那就有

\[\sum_{e_\in E_M}value(e)<\sum_{e_\in E_S}value(e) \]

思路:

先考虑最小生成树的 \(kruskal\) 算法。该算法是将图上的所有边给从小到大排序,在用并查集来判断该边是否是树边。最后找到 \(n-1\) 条边即是最小生成树了。而次小生成树也就只与最小生成树上有一个边的关系。证明很简单:若需要替换多条边,那这些边替换后必须保证树的连通。那也就要保证每个边替换后都要联通。既然要严格次小,与其说选择多条边替换为更大值,不如将一条边给替换掉即可。

那我们就要考虑替换那一条边。要替换的边肯定是在 \(kruskal\) 中没有选择到的边。那我们就可以枚举每一条没选到的边,然后在到树上选择适当的边来替换即可。首先先要确定那些边是可以替换的。我们发现若要保证替换后的边可以保证树连通。那只能替换改变所连接的两个节点所对应的路径。因为不能让新图成为一个环,就一定要断开一个,且若断开其他无关的边,那就会有无关的节点被孤立,使树无法联通。至于要替换哪一条,很明显是最大的边,这样就能使插值尽可能小。

实现:

若暴力枚举每一条边,在暴力枚举路径上的边,那复杂度就是 \(O(n^2)\) 。我们考虑求树上两点的路径时一般都会用到 \(lca\) 也就是最近公共祖先。而实现 \(lca\) 的方式有两种。一个是 倍增,另一个是树剖。可以发现。倍增就如同 \(st\) 表。既让如此,我们就可以在倍增求 \(lca\) 时顺便维护一下最大值。若用树剖的话,也可以用线段树来维护最大值。这样最后枚举所有边,在将答案取最小值即可。

注意:

1.若两个点间路径的最大值与要替换的边一样怎么办。因为这样替换后权值和不会发生变化。这样就需要在储存路径的次大值,若没有最大值,也就要返回 \(inf\)

2.由于存在自环,所以当遍历到自环边的时候,由于路径不存在边,那自然返回的为零。对于这种情况,只需特判一下自环跳过即可。

完整代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
struct node{
    int nxt,to,val;
}e[N]; 
int h[N],cnt;
void add(int u,int v,int w) {
    e[++cnt].nxt=h[u];
    e[cnt].to=v; 
    e[cnt].val=w;h[u]=cnt;
}
struct edge{
    int u,v,w;
    bool used;
    bool operator<(const edge &x)const{return w<x.w;}
}g[N];
int fa[N];
int find(int x){
    return fa[x]==x?x:fa[x]=find(fa[x]);
}
int n,m;
int ans0;
int vis[N];
void kurskal(){
    for(int i=1;i<=n;i++)fa[i]=i;
    sort(g+1,g+m+1);
    for(int i=1;i<=m;i++){
        int u=g[i].u,v=g[i].v,w=g[i].w;
        int fu=find(u),fv=find(v);
        if(fu==fv)continue;
        ans0+=w;
        fa[fu]=fv;
        add(u,v,w);
        add(v,u,w);
        g[i].used=true;
    }
}
int f[N][20],mx[N][20],smx[N][20],dep[N];
void dfs(int u){
    dep[u]=dep[f[u][0]]+1;
    for(int i=1;i<=18;i++){
        f[u][i]=f[f[u][i-1]][i-1];
        mx[u][i]=max(mx[u][i-1],mx[f[u][i-1]][i-1]);
        if(mx[u][i-1]==mx[f[u][i-1]][i-1]){
            smx[u][i]=max(smx[u][i-1],smx[f[u][i-1]][i-1]);
        }
        else{
            smx[u][i]=min(mx[u][i-1], mx[f[u][i-1]][i-1]);
        }
    }
    for(int i=h[u];i;i=e[i].nxt) {
        int v=e[i].to,w=e[i].val;
        if (v==f[u][0])continue;
        f[v][0]=u; 
        mx[v][0]=w; 
        dfs(v);
    }
}
int lca(int u,int v){
    if(dep[u]<dep[v])swap(u,v);
    for(int i=18;i>=0;i--){
        if(dep[f[u][i]]>=dep[v]){
            u=f[u][i];
        }
    }
    if(u==v)return u;
    for(int i=18;i>=0;i--){
        if(f[u][i]!=f[v][i]){
            u=f[u][i];
            v=f[v][i];
        }
    }
    return f[u][0];
}

int calc(int u,int v,int w){
    int l=lca(u,v);
    int max1=0,max2=0; 
    for(int i=18;i>=0;i--){
        if(dep[f[u][i]]>=dep[l]){
            if(max1==mx[u][i])max2=max(smx[u][i],max2);
            if(max1>mx[u][i])max2=max(mx[u][i],max2);
            if(max1<mx[u][i]){
                max2=max(max1,smx[u][i]);
                max1=mx[u][i];
            }
            u=f[u][i];
        }
        if(dep[f[v][i]]>=dep[l]){
            if(max1==mx[v][i]) max2=max(smx[v][i],max2);
            if(max1>mx[v][i]) max2=max(mx[v][i],max2);
            if(max1<mx[v][i]){
                max2=max(max1,smx[v][i]);
                max1=mx[v][i];
            }
            v=f[v][i];
        }
    }
    if(w!=max1)return ans0-max1+w;
    if(max2)return ans0-max2+w;
    return inf;
}
signed main(){
    scanf("%lld%lld",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%lld%lld%lld",&g[i].u,&g[i].v,&g[i].w);
    }
    kurskal();
    dfs(1);
    int ans=inf;
    for(int i=1;i<=m;i++){
        if(g[i].used || g[i].u==g[i].v)continue;
        ans=min(ans,calc(g[i].u,g[i].v,g[i].w));
    }
    printf("%lld\n",ans);
}
posted @ 2025-03-22 08:52  XichenOC  阅读(70)  评论(1)    收藏  举报