Label
严格次小生成树模板题
Description
给定一个
N
N
N个节点
M
M
M条无向边的带权图
(
N
≤
1
0
5
,
M
≤
3
×
1
0
5
,
边
权
∈
[
0
,
1
0
9
]
)
(N\leq10^5,M\leq3\times 10^5,边权\in [0,10^9])
(N≤105,M≤3×105,边权∈[0,109]),求无向图的严格次小生成树(题目保证一定有解且数据可能有自环)。设最小生成树
M
M
M的边权之和为
M
M
M,严格次小生成树的
S
S
S边权为
S
S
S,严格次小生成树就是指边权之和大于
M
M
M的生成树中边权之和最小的一个,即:
S
=
m
i
n
{
∑
e
∈
E
S
v
a
l
u
e
(
e
)
∣
∑
e
∈
E
S
v
a
l
u
e
(
e
)
<
∑
e
∈
E
M
v
a
l
u
e
(
e
)
}
S=min\{\sum_{e\in E_{S}}value(e)\mid\sum_{e\in E_{S}}value(e)<\sum_{e\in E_{M}}value(e)\}
S=min{e∈ES∑value(e)∣e∈ES∑value(e)<e∈EM∑value(e)}
Solution
根据Kruskal的贪心本质,我们可得到这么一个性质: ∀ e ( u , v ) ∉ M \forall e(u,v)\notin M ∀e(u,v)∈/M,有:最小生成树上 u u u到 v v v的路径边集内边的边权最大值 ≤ v a l u e ( e ) \leq value(e) ≤value(e)(证明:若 e ( u , v ) ∈ M e(u,v)\in M e(u,v)∈M,那么拿此条边替换掉 M M M内边权最大的边,根据树的连通性可得:替换后原图依然是一颗生成树且其权值必大于等于 M M M)。
那么,根据这个性质我们可直接推得:若保证图存在次小生成树,严格的次小生成树 S S S相比于 M M M只有一条边不同;或者说,一定含有且仅含有一条原来不在 M S T MST MST边集 M M M内的边(1、从 ∀ e ( u , v ) \forall e(u,v) ∀e(u,v)与 M M M上 u − > v u->v u−>v的最短路径构成的环的角度考虑边权大小,替换 k k k条边后生成树的边权显然小于在此基础上多替换一条边后生成树的边权;2、如果 S S S相比于 M M M有多于一条边不同,由于其边权和小于 M M M,故至少存在 1 1 1条边,它替换掉了 M S T MST MST内一条权值小于它的边,但如果存在多条边满足此情况, S S S便不再是严格次小生成树,矛盾。综上此结论成立)。
所以,我们寻找严格次小生成树,只需逐个枚举所有未被添入 M S T MST MST内的所有非自环边。假设规定前述 S S S内唯一一条 ∉ M \notin M ∈/M的边为 e ( u , v ) e(u,v) e(u,v),我们考虑将其替换 M M M上 u − > v u->v u−>v的最短路径内任意一条边,根据树的连通性可保证替换后该图一定是树。而根据严格次小生成树的定义,被替换的边一定是 u − > v u->v u−>v最短路径边集内权值最大或次大(最大权值等于 v a l u e ( e ) value(e) value(e)时则为次大权值边,否则得到的树边权和与 M S T MST MST相同,不满足严格次小生成树定义)的边。
u − > v u->v u−>v最短路径上最大与次大边权可以通过求 L C A LCA LCA倍增维护,但需要注意的是:这一部分无论是代码逻辑还是特殊情况,细节都不算不少(譬如次大值数组的更新问题与查询时如何组织代码块更加简洁(譬如下面代码里的Qmax函数: u − > v u->v u−>v最短路径上次大值转化为分别求 u − > l c a ( u , v ) u->lca(u,v) u−>lca(u,v)与 v − > l c a ( u , v ) v->lca(u,v) v−>lca(u,v)最短路径上的最/次大值后合并,从而省去大量冗余代码))。
Code
#include<cstdio>
#include<iostream>
#include<algorithm>
#define ri register int
#define ll long long
using namespace std;
const int MAXN=6e5+20,NMAXN=1e5+20;
int N,M,u[MAXN],v[MAXN],fst[MAXN],nxt[MAXN],Fa[MAXN],vis[MAXN],fau,fav,tot,dep[MAXN],fa[NMAXN][18];
ll w[MAXN],ansk,maxw[NMAXN][18],cimaxw[NMAXN][18],maxci,anst,ans;
struct node{
int U,V,num;
ll W;
}E[MAXN];
bool cmp(const node &a,const node &b){
return a.W < b.W;
}
ll Max(ll x,ll y){
return (x>y)?x:y;
}
ll Min(ll x,ll y){
return (x<y)?x:y;
}
int Find(int x)
{
if(x==Fa[x]) return x;
return Fa[x]=Find(Fa[x]);
}
void Kruskal()
{
M >>=1;
sort(E+1,E+M+1,cmp);
for(ri i=1;i<=N;++i) Fa[i]=i;
for(ri i=1;i<=M;++i)
{
if(E[i].U==E[i].V) continue;
fau=Find(E[i].U); fav=Find(E[i].V);
if(fau!=fav)
{
++tot; Fa[fau]=fav; vis[E[i].num]=1;
ansk+=E[i].W;
if(tot+1==N) break;
}
}
}
void dfs(int x,int father)
{
fa[x][0]=father; dep[x]=dep[fa[x][0]]+1; cimaxw[x][0]=maxw[x][0];
for(ri k=1;(1<<k)<=dep[x];++k)
{
fa[x][k]=fa[fa[x][k-1]][k-1];
maxw[x][k]=Max(maxw[x][k-1],maxw[fa[x][k-1]][k-1]);
//注意下面次小值的比较顺序,有讲究(逻辑混乱写出来也会冗余且混乱)
if(k==1)//特判1
{
cimaxw[x][k]=Min(cimaxw[x][k-1],cimaxw[fa[x][k-1]][k-1]);
continue;
}
maxci=Max(cimaxw[x][k-1],cimaxw[fa[x][k-1]][k-1]);
if((maxci==maxw[x][k-1])&&(maxci==maxw[fa[x][k-1]][k-1]))
cimaxw[x][k]=Min(cimaxw[x][k-1],cimaxw[fa[x][k-1]][k-1]);
//特判2:这个if显然是为了防止条件中的情况
else
cimaxw[x][k]=maxci;
if(maxw[x][k-1]<maxw[fa[x][k-1]][k-1])
cimaxw[x][k]=Max(cimaxw[x][k],maxw[x][k-1]);
if(maxw[x][k-1]>maxw[fa[x][k-1]][k-1])
cimaxw[x][k]=Max(cimaxw[x][k],maxw[fa[x][k-1]][k-1]);
//为什么不考虑maxw[x][k-1]==maxw[fa[x][k-1]][k-1]的情况:
//若maxw[x][k-1]=maxw[fa[x][k-1]][k-1],除非两端的次大值全部等于最大值(即路径上边权相同)
//否则cimaxw[x][k]一定不等于maxw[x][k-1],即maxw[x][k],进而导致次小值信息丢失。
}
for(ri k=fst[x];k>0;k=nxt[k])
{
ri tmp=k>>1;
if(k%2==1) ++tmp;
if(vis[tmp]&&(v[k]!=fa[x][0]))
{
maxw[v[k]][0]=w[k];
dfs(v[k],x);
}
}
}
ll Lca(ll x,ll y)
{
if(dep[x]<dep[y]) swap(x,y);
for(ri k=17;k>=0;--k)
if(dep[x]-(1<<k)>=dep[y]) x=fa[x][k];
if(x==y) return x;
for(ri k=17;k>=0;--k)
if(fa[x][k]!=fa[y][k])
{
x=fa[x][k];
y=fa[y][k];
}
return fa[x][0];
}
ll Qmax(ll x,ll f,ll val)//为了节省码量,不再在求LCA的过程中直接求被替换的边的权值
//val的作用:路径倍增过程中凡是等于val的部分的最大次大值不再
//参与答案更新,从而很好且简洁地保证了信息不会被丢失
{
ll now=-1;
for(ri k=17;k>=0;--k)
if(dep[fa[x][k]]>=dep[f])
{
if(maxw[x][k]<val) now=max(now,maxw[x][k]);
if(cimaxw[x][k]<val) now=max(now,cimaxw[x][k]);
x=fa[x][k];
}
return now;
}
int main()
{
scanf("%d%d",&N,&M);
M <<=1;
for(ri i=1;i<=M;i+=2)
{
scanf("%d%d%lld",&u[i],&v[i],&w[i]);
E[(i+1)>>1].U=u[i]; E[(i+1)>>1].V=v[i]; E[(i+1)>>1].W=w[i]; E[(i+1)>>1].num=(i+1)>>1;
nxt[i]=fst[u[i]]; fst[u[i]]=i;
u[i+1]=v[i]; v[i+1]=u[i]; w[i+1]=w[i];
nxt[i+1]=fst[u[i+1]]; fst[u[i+1]]=i+1;
}
Kruskal();
dep[0]=-1; dfs(1,0);
ans=4e14;
for(ri i=1;i<=M;++i)
{
if((E[i].U==E[i].V)||vis[E[i].num]) continue;
int lca=Lca(E[i].U,E[i].V);
ll qu=Qmax(E[i].U,lca,E[i].W),qv=Qmax(E[i].V,lca,E[i].W);
anst=-1;
if(qu>=0) anst=ansk-qu+E[i].W;
if(qv>=0) anst=Min(anst,ansk-qv+E[i].W);
if(anst>0) ans=Min(ans,anst);
//anst=-1:特判u->v路径上边权全相同的情况
//此外,由于边权可能为0,故anst初始化为-1
}
cout<<ans;
return 0;
}