背景:
远古时代的坑了。
题目传送门:
https://www.luogu.org/problemnew/show/P4180
题意:
求一棵树的严格次小生成树的边权和(边权和严格大于最小生成树的生成树)。
思路:
维护最小生成树的一种常见做法就是
L
C
T
LCT
LCT。
而
L
C
T
LCT
LCT维护最小生成树又分为两种:在线和离线。详见:
L
C
T
LCT
LCT维护最小生成树。
而离线的显然更好打。
考虑
K
r
u
s
k
a
l
Kruskal
Kruskal的贪心做法。先排序,按照边权升序。若当前两点不连通,则连同它们,同时更新答案。离线的
L
C
T
LCT
LCT用类似的思想。
有一个性质(定理什么的):最小生成树与次小生成树(不一定严格)最多只有一条边的权值不等(自己想想,还是不难证的,用一下反证法,再考虑选这条边的是否必要性)。
用这个性质,我们枚举不在最小生成树中的边,看其能否更新此时的次小生成树(因为要严格次小,所以若这两个点对之间所有边长的最大值
=
=
=当前这一条边的值,那么无论怎么更新得到的还是非严格次小生成树(边权和
=
=
=最小生成树的和),这时就要用次大值更新;否则用最大值更新),若可以,则更新,并求出差值(最小生成树与严格次小生成树的边权和的差)的最小值。更新差值的比较途径:与当前最小生成树比;或与当前次小生成树比。
最后用最小生成树的边权和加上这个差值即可。
代码:
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
struct node1{int d,fa,ma1,ma2,lazy,son[2];} tr[600010];
struct node2{int x,y,z;} a[600010];
bool bz[600010];
int n,m;
LL ans=0,cha=2147483647;
inline int read()
{
int x=0,f=1;
char ch=getchar();
for(;ch<'0'||ch>'9';ch=getchar())
if(ch=='-') f=-1;
for(;ch>='0'&&ch<='9';x=(x<<3)+(x<<1)+(ch^48),ch=getchar());
return x*f;
}
bool isroot(int x)
{
return tr[tr[x].fa].son[0]!=x&&tr[tr[x].fa].son[1]!=x;
}
bool isson(int x)
{
return x==tr[tr[x].fa].son[1];
}
void change(int x)
{
if(!x)return;
swap(tr[x].son[0],tr[x].son[1]);
tr[x].lazy^=1;
}
void pushup(int x)
{
int lc=tr[x].son[0],rc=tr[x].son[1];
tr[x].ma1=tr[x].d;
tr[x].ma2=max(tr[lc].ma2,tr[rc].ma2);
if(tr[lc].ma1>tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[x].ma1),tr[x].ma1=tr[lc].ma1;
else if(tr[lc].ma1<tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[lc].ma1);
if(tr[rc].ma1>tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[x].ma1),tr[x].ma1=tr[rc].ma1;
else if(tr[rc].ma1<tr[x].ma1) tr[x].ma2=max(tr[x].ma2,tr[rc].ma1);
}
void pushdown(int x)
{
if(!tr[x].lazy) return;
change(tr[x].son[0]),change(tr[x].son[1]);
tr[x].lazy=0;
}
void rot(int x)
{
int w=isson(x),y=tr[x].fa,yy=tr[y].fa;
tr[y].son[w]=tr[x].son[w^1];
if(tr[y].son[w]) tr[tr[y].son[w]].fa=y;
tr[x].fa=yy;
if(!isroot(y)) tr[yy].son[isson(y)]=x;
tr[x].son[w^1]=y;tr[y].fa=x;
pushup(y);
}
int sta[300010];
int top;
void splay(int x)
{
sta[top=1]=x;
for(int i=x;!isroot(i);i=tr[i].fa)
sta[++top]=tr[i].fa;
while(top) pushdown(sta[top--]);
for(int y=tr[x].fa;!isroot(x);rot(x),y=tr[x].fa)
if(!isroot(y)) isson(x)^isson(y)?rot(x):rot(y);
pushup(x);
}
void access(int x)
{
for(int y=0;x;y=x,x=tr[x].fa)
splay(x),tr[x].son[1]=y,pushup(x);
}
int findroot(int x)
{
access(x);splay(x);
while(tr[x].son[0]) x=tr[x].son[0];
//splay(x);
return x;
}
void makeroot(int x)
{
access(x);splay(x);change(x);
}
void link(int x,int y)
{
makeroot(x);
if(findroot(y)!=x) tr[x].fa=y;
}
void split(int x,int y)
{
makeroot(x);access(y);splay(y);
}
node1 query(int x,int y)
{
split(x,y);
return tr[y];
}
bool cmp(node2 x,node2 y)
{
return x.z<y.z;
}
int main()
{
n=read(),m=read();
for(int i=0;i<=(n<<1);i++)
tr[i]=(node1){0,0,0,0,0,0,0};
for(int i=n+1;i<=n+m;i++)
a[i].x=read(),a[i].y=read(),a[i].z=read();
sort(a+n+1,a+n+m+1,cmp);
for(int i=n+1;i<=n+m;i++)
if(findroot(a[i].x)!=findroot(a[i].y))
{
tr[i].d=a[i].z;
link(a[i].x,i);link(a[i].y,i);
ans+=a[i].z;
bz[i]=true;
}
for(int i=n+1;i<=n+m;i++)
{
if(bz[i]) continue;
node1 NOW=query(a[i].x,a[i].y);
if(a[i].z==NOW.ma1) cha=min(cha,(LL)a[i].z-NOW.ma2); else cha=min(cha,(LL)a[i].z-NOW.ma1);
}
printf("%lld",ans+cha);
}