次小生成树有严格次小生成树和非严格次小生成树之分。常见的是严格次小生成树。
严格次小生成树的定义如下:
如果最小生成树选择的边集是 E M E_M EM,严格次小生成树选择的边集是 E S E_S ES,那么需要满足:( v a l u e ( e ) value(e) value(e) 表示边 e e e 的权值) ∑ e ∈ E M v a l u e ( e ) < ∑ e ∈ E S v a l u e ( e ) \sum_{e \in E_M}value(e)<\sum_{e \in E_S}value(e) ∑e∈EMvalue(e)<∑e∈ESvalue(e)。
——P4180 [BJWC2010] 严格次小生成树
求严格次小生成树的方法主要有倍增、树剖、LCT 等。这里介绍所用算法较基础的 Kruskal+倍增+LCA 的方法。
显然,次小生成树是在最小生成树的基础上替换一条边之后形成的。
容易想到一种思路:对于每一条非树边,尝试把它加入最小生成树中。此时图中出现了一个环,删去环中边权最大的树边即可。由于要求的是严格次小,为了避免最大树边等于加入的这条非树边的边权的情况,还需要维护次大树边。
维护次大树边可以用倍增。用 g 1 ( i , j ) , g 2 ( i , j ) g_1(i,j),g_2(i,j) g1(i,j),g2(i,j) 分别表示从 i i i 到 i i i 的 2 j 2^j 2j 级父亲的路径上的边权最大值、次大值。 g 1 g_1 g1 直接维护即可, g 2 g_2 g2 的 max \max max 选择需要分情况讨论:
时间复杂度 O ( n log n + m log n ) O(n\log n+m\log n) O(nlogn+mlogn)。
#include
using namespace std;
#define int long long
const int maxn=1e5+5,maxm=6e5+5;
int head[maxn],g1[maxn][25],g2[maxn][25],f[maxn][25],dep[maxn],cnt,N,M,fa[maxn],sum;
struct edge1{int u,v,w;}e1[maxm];
struct edge2{int to,nxt,w;}e2[maxm];
bool vis[maxm];
bool cmp(edge1 a,edge1 b){return a.w<b.w;}
int find(int x){return x==fa[x]?x:fa[x]=find(fa[x]);}
void add(int x,int y,int z){e2[++cnt]={y,head[x],z},head[x]=cnt;}
void kruskal()
{
sort(e1+1,e1+M+1,cmp);
int tot=0;
for(int i=1;i<=M;i++)
{
int fu=find(e1[i].u),fv=find(e1[i].v);
if(fu!=fv) tot++,fa[fu]=fv,sum+=e1[i].w,add(e1[i].u,e1[i].v,e1[i].w),add(e1[i].v,e1[i].u,e1[i].w),vis[i]=1;
if(tot==N-1) break;
}
}
void dfs(int x,int fa)
{
dep[x]=dep[fa]+1,f[x][0]=fa;
for(int i=1;i<=20;i++)
{
f[x][i]=f[f[x][i-1]][i-1];
g1[x][i]=max(g1[f[x][i-1]][i-1],g1[x][i-1]);
if(g1[f[x][i-1]][i-1]==g1[x][i-1]) g2[x][i]=max(g2[x][i-1],g2[f[x][i-1]][i-1]);
else if(g1[f[x][i-1]][i-1]>g1[x][i-1]) g2[x][i]=max(g1[x][i-1],g2[f[x][i-1]][i-1]);
else g2[x][i]=max(g2[x][i-1],g1[f[x][i-1]][i-1]);
}
for(int i=head[x];i;i=e2[i].nxt)
{
if(e2[i].to==fa) continue;
g1[e2[i].to][0]=e2[i].w;
dfs(e2[i].to,x);
}
}
int lca(int x,int y)
{
if(dep[x]<dep[y]) swap(x,y);
for(int i=20;i>=0;i--) if(dep[f[x][i]]>=dep[y]) x=f[x][i];
if(x==y) return x;
for(int i=20;i>=0;i--)
if(f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int getmx(int x,int rt,int w)
{
int ans=0;
for(int i=20;i>=0;i--)
if(dep[f[x][i]]>=dep[rt])
{
if(g1[x][i]==w) ans=max(ans,g2[x][i]);
else ans=max(ans,g1[x][i]);
x=f[x][i];
}
return ans;
}
signed main()
{
cin>>N>>M;
for(int i=1;i<=M;i++) cin>>e1[i].u>>e1[i].v>>e1[i].w;
for(int i=1;i<=N;i++) fa[i]=i;
kruskal();
dfs(1,0);
int ans=LLONG_MAX;
for(int i=1;i<=M;i++)
{
if(e1[i].u==e1[i].v||vis[i]) continue;
int mx=getmx(e1[i].u,lca(e1[i].u,e1[i].v),e1[i].w),my=getmx(e1[i].v,lca(e1[i].u,e1[i].v),e1[i].w);
if(max(mx,my)!=e1[i].w) ans=min(ans,sum+e1[i].w-max(mx,my));
}
cout<<ans<<endl;
// for(int i=1;i<=N;i++) cout<
return 0;
}