题意
给定一个 (n) 个点 (m) 条边的有权无向图,求出原图的一棵生成树使得该树上最大边权与最小边权的差值最小。
( exttt{Data Range:}1leq nleq 5 imes 10^4,1leq mleq 2 imes 10^5,1leq wleq 5 imes 10^4)
题解
这位傻逼把 (n) 个节点的树所含有的边的数目认为是 (n),还在出现环的时候立即统计答案(可能生成树都没构建出来),导致 ( exttt{WA}) 了一发又一发,请大家不要学他。
直接进入正题。
考虑先将边权从小到大排序,然后一条一条的考虑。
如果这条边加入当前的图中不会成环的话,那么直接加进来即可。
如果成了环的话,考虑在环上删去边权最小的边。
为什么这么做是正确的呢?
假设我们现在考虑的是第 (k) 条边,连接 (u_i) 和 (v_i),边权为 (w_i),讨论一下当前图中边权最小的边的位置。
-
当这条边的位置在环上的时候,由于新图的边权最小值改变了,所以需要更新答案。
-
当这条边的位置不在环上的时候,对答案无影响。
所以我们每一次需要更新答案的时候我们都进行了更新,自然答案就是对的了。
加入完这条边之后,如果当前的图是原图的一棵生成树的话那么更新一下答案就好了。
注意到整个过程涉及到删边和连边操作,所以采用 ( exttt{LCT}) 来维护生成树。
更新答案时,由于要找到最小边权的边的位置,并且每一次加完边之后最小边权的边的位置单调不降(因为排过序了),打标记+移动指针就可以了。
代码
#include<bits/stdc++.h>
using namespace std;
typedef int ll;
typedef long long int li;
const ll MAXN=3e5+51;
struct Edge{
ll from,to,dist;
inline bool operator <(const Edge &rhs)const
{
return dist<rhs.dist;
}
};
ll n,m,ptr=1,cur,x,y,offset=60000,res;
Edge ed[MAXN];
ll ffa[MAXN],vis[MAXN];
inline ll read()
{
register ll num=0,neg=1;
register char ch=getchar();
while(!isdigit(ch)&&ch!='-')
{
ch=getchar();
}
if(ch=='-')
{
neg=-1;
ch=getchar();
}
while(isdigit(ch))
{
num=(num<<3)+(num<<1)+(ch-'0');
ch=getchar();
}
return num*neg;
}
inline ll find(ll x)
{
return x==ffa[x]?x:ffa[x]=find(ffa[x]);
}
namespace LCT{
struct Node{
ll fa,mn,val,rv,sz;
ll ch[2];
};
struct LinkCutTree{
Node nd[MAXN];
ll st[MAXN];
#define ls nd[x].ch[0]
#define rs nd[x].ch[1]
inline bool nroot(ll x)
{
return nd[nd[x].fa].ch[0]==x||nd[nd[x].fa].ch[1]==x;
}
inline ll get(ll x,ll y)
{
return nd[x].val<nd[y].val?x:y;
}
inline void update(ll x)
{
nd[x].mn=get(x,get(nd[ls].mn,nd[rs].mn));
}
inline void reverse(ll x)
{
swap(ls,rs),nd[x].rv^=1;
}
inline void spread(ll x)
{
if(nd[x].rv)
{
ls?reverse(ls):(void)(1),rs?reverse(rs):(void)(1);
nd[x].rv=0;
}
}
inline void rotate(ll x)
{
ll fa=nd[x].fa,gfa=nd[fa].fa;
ll dir=nd[fa].ch[1]==x,son=nd[x].ch[!dir];
if(nroot(fa))
{
nd[gfa].ch[nd[gfa].ch[1]==fa]=x;
}
nd[x].ch[!dir]=fa,nd[fa].ch[dir]=son;
if(son)
{
nd[son].fa=fa;
}
nd[fa].fa=x,nd[x].fa=gfa,update(fa);
}
inline void splay(ll x)
{
ll fa=x,gfa,cur=0;
st[++cur]=fa;
while(nroot(fa))
{
st[++cur]=fa=nd[fa].fa;
}
while(cur)
{
spread(st[cur--]);
}
while(nroot(x))
{
fa=nd[x].fa,gfa=nd[fa].fa;
if(nroot(fa))
{
rotate((nd[fa].ch[0]==x)^(nd[gfa].ch[0]==fa)?x:fa);
}
rotate(x);
}
update(x);
}
inline void access(ll x)
{
for(register int i=0;x;x=nd[i=x].fa)
{
splay(x),rs=i,update(x);
}
}
inline void makeRoot(ll x)
{
access(x),splay(x),reverse(x);
}
inline void split(ll x,ll y)
{
makeRoot(x),access(y),splay(y);
}
inline void link(ll x)
{
makeRoot(ed[x].from);
nd[nd[ed[x].from].fa=x+offset].fa=ed[x].to;
nd[x+offset].val=ed[x].dist,update(x);
}
inline void cut(ll x)
{
access(ed[x-offset].from),splay(x);
ls=rs=nd[ls].fa=nd[rs].fa=0,update(x);
}
#undef ls
#undef rs
};
}
LCT::LinkCutTree lct;
int main()
{
n=read(),m=read();
for(register int i=1;i<=m;i++)
{
ed[i].from=read(),ed[i].to=read(),ed[i].dist=read();
}
for(register int i=0;i<=n;i++)
{
ffa[i]=i,lct.nd[i].val=0x7fffffff;
}
sort(ed+1,ed+m+1);
for(register int i=1;i<=m;i++)
{
if(find(x=ed[i].from)!=find(y=ed[i].to))
{
vis[i]=1,lct.link(i),cur++,ffa[ffa[y]]=ffa[x];
if(cur==n-1)
{
res=ed[i].dist-ed[ptr].dist;
}
}
else
{
if(x==y)
{
continue;
}
lct.split(x,y),vis[lct.nd[y].mn-offset]=0;
lct.cut(lct.nd[y].mn),lct.link(i),vis[i]=1;
while(!vis[ptr])
{
ptr++;
}
if(cur==n-1)
{
res=min(res,ed[i].dist-ed[ptr].dist);
}
}
}
printf("%d
",res);
}