边权和严格大于最小生成树的边权和最小的生成树称为严格次小生成树
根据kruskal的求解原理,每次贪心求局部最优,从而得到全局最优,那么求解次小生成树的过程就是把某一局部最优变成次优,从而得到全局次最优
回顾求解最小生成树的过程,当我们添边组成树时,如果此时树成环,说明有一条边是多余的,我们删除这个环上的一条边也不会影响连通性,所以为使总权值最小,我们贪心地删去环中权值最大的边,而由于kruskal算法每次新添加的边都是树中的最大值,所以当求解次小生成树时只需删除环中次大的边就可以了
考虑如何找到环中次大边,有这样一条性质:x, y之间的环为 x->lca(x,y)->y;这里要用到倍增求最近公共祖先(lca),于是我们倍增的遍历求lca的过程,就是遍历环的过程,为与倍增遍历配合我们开一个max1[v][j]数组表示节点v到它的第 2^j 个父节点直接路径的边权最大值,转移方程为
max1[v][j]=max(max1[f[v][j-1]][j-1],max1[v][j-1]); 其中 f[ ][ ]数组为倍增数组、
同时由于所求为严格次小生成树,为避免与最大值相等的情况,还需开个g[][]数组表示次大
转移过程见代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll N=3e5+2;
struct Node{
int u,v;
ll w;
bool flag=0;
bool operator<(Node s){
return w<s.w;
}
}e[N];
inline ll read(){
ll x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-')
f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
x=(x<<1)+(x<<3)+(ch^48);
ch=getchar();
}
return x*f;
}
int head[100005],idx=1,d[100005],n,m;
int father[100005];
ll g[100005][18],max1[100005][18],sum,ans,lg[20];
int f[100005][18];
vector<pair<ll,ll>>vc[100005];
inline ll find(ll x){
if(father[x]==x) return x;
return father[x]=find(father[x]);
}
void Kurskal(){
ll fa,fb,q=1;
sort(e+1,e+1+m);
for(ll i=1;i<=n;i++) father[i]=i;
for(ll i=1;i<=m;i++){
fa=find(e[i].u);
fb=find(e[i].v);
if(fa!=fb){
q++;
e[i].flag=1;
vc[e[i].u].push_back({e[i].v,e[i].w});
vc[e[i].v].push_back({e[i].u,e[i].w});
father[fa]=fb;
sum+=e[i].w;
}
if(q==n) break;
}
}
void dfs(ll u){
ll v,w;
for(ll i=0;i<vc[u].size();i++){
v=vc[u][i].first,w=vc[u][i].second;
if(v!=f[u][0]){
d[v]=d[u]+1;//记录节点的高度
f[v][0]=u;
max1[v][0]=w;
for(ll j=1;j<=17;j++){
if(lg[j]>d[v]) break;
f[v][j]=f[f[v][j-1]][j-1];//倍增数组
max1[v][j]=max(max1[f[v][j-1]][j-1],max1[v][j-1]);//最大值数组
g[v][j]=max(g[v][j-1],g[f[v][j-1]][j-1]);//次大值数组
if(max1[f[v][j-1]][j-1]!=max1[v][j-1])
g[v][j]=max(g[v][j],min(max1[v][j-1],max1[f[v][j-1]][j-1]));
}
dfs(v);
}
}
}
void lca(ll u,ll v,ll w){
if(d[u]<d[v]) swap(u,v);
ll t=d[u]-d[v];
for(ll i=0;lg[i]<=t;i++){
if(t&lg[i]){
if(w==max1[u][i])
ans=max(ans,g[u][i]);
else
ans=max(ans,max1[u][i]);
u=f[u][i];
}
}
if(u==v) return;
for(ll i=17;i>=0;i--){
if(f[u][i]!=f[v][i]){
if(w==max1[u][i]) ans=max(ans,g[u][i]);
else ans=max(ans,max1[u][i]);
if(w==max1[v][i]) ans=max(ans,g[v][i]);
else ans=max(ans,max1[v][i]);
u=f[u][i];
v=f[v][i];
}
}
if(w==max1[u][0]) ans=max(ans,g[u][0]);
else ans=max(ans,max1[u][0]);
if(w==max1[v][0]) ans=max(ans,g[v][0]);
else ans=max(ans,max1[v][0]);
}
void work(){
ll imin=1e9;
for(ll i=1;i<=m;i++){
if(!e[i].flag){
ans=-1;
lca(e[i].u,e[i].v,e[i].w);
if(ans!=-1)
imin=min(imin,e[i].w-ans);
}
}
printf("%lld\n",sum+imin);
}
int main(){
// 1.存边
// 2.kruskal求最小生成树,建树
// 3.dfs求倍增数组和路径最大值与次大值数组
// 4.求lca的过程就是倍增遍历环中路径的过程,从而得到环中路径次大值
// 更新,求最接近最小生成树边权和的值
lg[0]=1;
for(ll i=1;i<=17;i++)
lg[i]=lg[i-1]*2;
n=read(),m=read();
for(ll i=1;i<=m;i++){
e[i].u=read();
e[i].v=read();
e[i].w=read();
}
Kurskal();
dfs(1);
work();
return 0;
}