次小生成树
非严格次小生成树
在无向图中,边权和最小的满足边权和 大于等于 最小生成树边权和的生成树
如何求解?
先求出最小生成树,设其权值和为 \(sum\)
首先要知道,对于 \(u,v\) 两点,他们在最小生成树上的路径中,权值最大值肯定要小于等于边 \((u,v)\) 的权值(如果有的话)
因为如果他们路径上的最大值比 \((u,v)\) 边权大,那么肯定就要去掉那条最大的边,换成 \((u,v)\),这样做一样能保证是一颗树,且边权和更小
那么,可以依次对于每条边 \((u,v,w)\),找出 \((u,v)\) 在最小生成树上的权值最大值,设其为 \(max_w\)
我们尝试用 \((u,v,w)\) 来替换掉找出的那条边权最大的边
则可以用 \(sum-max_w+w\) 来更新答案
答案的最小值,也就是 \(\min(sum-max_w+w)\) 即为最终答案
这样做一定能找到非严格次小生成树之一
因为在所有非严格次小生成树中(显然可能有多个),一定会有一个是由最小生成树仅更换一条边得来
由于上面说的,\(u,v\) 两点,他们在最小生成树上的路径中,权值最大值肯定要小于等于边 \((u,v)\) 的权值,所以可以大体的理解为 “更换的边越多,权值和会变得越大”
当然这并不是严格的证明
对于如何找边权最大的那条边,在最小生成树上用倍增维护即可
由于没有题,代码没写
严格次小生成树
在无向图中,边权和最小的满足边权和 严格大于 最小生成树边权和的生成树
考虑为什么上面个说的方法求出的次小生成树“不严格”
\(u,v\) 两点,他们在最小生成树上的路径中,权值最大值肯定要 小于等于 边 \((u,v)\) 的权值
这句话复制了第三遍了
是小于等于,所以如果最小生成树中,被替换掉的那条边,和替换他的那条边,权值相等的话,求出的次小生成树就和最小生成树的权值和相等
因此不严格
所以,只要倍增时,维护一个最小生成树上路径中的最大值,同时也维护次大值
如果最大值和 \((u,v)\) 的边权相等,那么就用次大值更新
这样就保证了找出的次小生成树严格大于最小生成树
题目:luogu P4180 [BJWC2010]严格次小生成树,LOJ#10133. 「一本通 4.4 例 4」次小生成树
另外代码中有个玄学问题,题目范围事 \(n\le 10^5\),但卡着比 \(10^5\) 多点并不能过,会在最后一个点越界
要再开大一些,听说是因为最后一个点有自环,但是我把自环判掉,如果不开大数组还是不能过
真 · 玄 学
所以就往大里开就好了
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<map>
#include<iomanip>
#include<cstring>
#define reg register
#define EN std::puts("")
#define LL long long
inline int read(){
register int x=0;register int y=1;
register char c=std::getchar();
while(c<'0'||c>'9'){if(c=='-') y=0;c=std::getchar();}
while(c>='0'&&c<='9'){x=x*10+(c^48);c=std::getchar();}
return y?x:-x;
}
#define N 1000006
#define M 300006
int n,m;
struct data{
int u,v,val;
}e[M];
int up[N],on_tree[N];
int fir[N],nex[2*N],to[2*N],w[2*N],tot;
int max[23][N],max2[23][N];
int deep[N],fa[23][N];
inline void add(int u,int v,int val){
to[++tot]=v;w[tot]=val;
nex[tot]=fir[u];fir[u]=tot;
}
inline int cmp(data aa,data aaa){return aa.val<aaa.val;}
inline int find(int x){
return up[x]==x?x:up[x]=find(up[x]);
}
inline LL kruskal(){
std::sort(e+1,e+1+m,cmp);
reg LL sum=0;
for(reg int i=1;i<=n;i++) up[i]=i;
for(reg int u,v,i=1,cnt=0;cnt<n-1;i++){
u=find(e[i].u);v=find(e[i].v);
if(u==v) continue;
up[u]=v;cnt++;
add(e[i].u,e[i].v,e[i].val);add(e[i].v,e[i].u,e[i].val);
sum+=e[i].val;
on_tree[i]=1;
}
return sum;
}
void dfs(int u,int father){
deep[u]=deep[father]+1;fa[0][u]=father;
max2[0][u]=-1e9;//不存在次大值时,把次大值设为 -inf
for(reg int v,i=fir[u];i;i=nex[i]){
v=to[i];
if(v==father) continue;
max[0][v]=w[i];
dfs(v,u);
}
}
inline void pre(){
int tmp[4];
for(reg int i=1;i<=20;i++)
for(reg int j=1;j<=n;j++){
fa[i][j]=fa[i-1][fa[i-1][j]];
tmp[0]=max[i-1][j];tmp[1]=max[i-1][fa[i-1][j]];
tmp[2]=max2[i-1][j];tmp[3]=max2[i-1][fa[i-1][j]];
//最大和次大值从上面四个值中得出
std::sort(tmp,tmp+4);
max[i][j]=tmp[3];
int pos=2;
while(pos>=0&&tmp[pos]==tmp[3]) pos--;
max2[i][j]=pos==-1?-1e9:tmp[pos];
}
}
inline int get_lca(int a,int b){
if(deep[a]<deep[b]) a^=b,b^=a,a^=b;
for(reg int i=20;~i;i--)
if(deep[fa[i][a]]>=deep[b]) a=fa[i][a];
if(a==b) return a;
for(reg int i=20;~i;i--)if(fa[i][a]!=fa[i][b]){
a=fa[i][a];b=fa[i][b];
}
return fa[0][a];
}
inline int get_max(int a,int b,int val){
int ret=-1e9;
for(reg int i=20;~i;i--)if(deep[fa[i][a]]>=deep[b]){
if(val!=max[i][a]) ret=std::max(ret,max[i][a]);
else ret=std::max(ret,max2[i][a]);
a=fa[i][a];
}
return ret;
}
int main(){
n=read();m=read();
for(reg int i=1;i<=m;i++){
e[i].u=read();e[i].v=read();e[i].val=read();
}
LL sum=kruskal(),ans=2e18;
dfs(1,0);
pre();
for(reg int i=1;i<=m;i++)if(!on_tree[i]){
int lca=get_lca(e[i].u,e[i].v);
LL tmpu=get_max(e[i].u,lca,e[i].val);
LL tmpv=get_max(e[i].v,lca,e[i].val);
if(std::max(tmpu,tmpv)>-1e9)
ans=std::min(ans,sum-std::max(tmpu,tmpv)+e[i].val);
}
std::printf("%lld",ans==2e18?-1:ans);
return 0;
}