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);
}