题目描述
给定一个标号为从 1 到 n 的、有 m 条边的无向图,求边权最大值与最小值的差值最小的生成树。
输入格式:
第一行两个数 n, m ,表示图的点和边的数量。
第二行起 m 行,每行形如$ u_i, v_i, w_i$ ,代表 $u_i$ 到 $v_i$有一条长为 $w_i$的无向边。
输出格式:
输出一行一个整数,代表你的答案。
数据保证存在至少一棵生成树。
输入样例#1:
4 6
1 2 10
1 3 100
1 4 90
2 3 20
2 4 80
3 4 40
输出样例#1:
20
说明
对于 30% 的数据,满足1≤n≤100,1≤m≤1000
对于 97% 的数据,满足 1≤n≤500,1≤m≤100000
对于 100% 的数据,满足 100001≤n≤50000,1≤m≤200000,1≤wi≤10000
***************************
题目分析:
对于要维护边权的lct
通常的做法是给每条边编号为n+1到n+m
对于link(u,v)操作
假设链接u和v的边编号为num
那么我们分别link(u,num),link(v,num)
相对的cut(u,v)
我们分别cut(u,num),cut(v,num)
知道再怎样维护边权后
对于这道题
先将边按边权升序排序
依次遍历每条边
若当前边 i 的两端结点u,v不连通,就link(u,i+n),link(v,i+n)
若当前边 i 的两端结点u,v已连通
找到树上u到v路径上边权最小的边num
先断开num这条边,再加入 i 这条边
即cut(u,num),cut(v,num)
link(u,i+n),link(v,i+n)
每便利一条边
若当前树上边数==n-1,就更新答案
对于更新答案时树上最小边权
需要一个vis数组判断第i号边是否在树上
并在每次断边后更新最小边权
具体见代码
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<‘0‘||ss>‘9‘){if(ss==‘-‘)f=-1;ss=getchar();}
while(ss>=‘0‘&&ss<=‘9‘){x=x*10+ss-‘0‘;ss=getchar();}
return f*x;
}
int n,m;
struct node{int u,v,dis;}E[200010];
int ch[500010][2],fa[500010],lzy[500010];
int val[500010],minn[500010];
int st[500010];
int vis[500010];//i号边是否在树上
int mx,mi=1,ans=1e9;//mx树上当前最大边权编号,mi树上当前最小边权编号
bool cmp(node a,node b){return a.dis<b.dis;}
void update(int x)
{
minn[x]=x;
minn[x]=val[minn[ch[x][0]]]<val[minn[x]] ?minn[ch[x][0]]:minn[x];
minn[x]=val[minn[ch[x][1]]]<val[minn[x]] ?minn[ch[x][1]]:minn[x];
}
void push(int x)
{
if(!lzy[x]) return;
swap(ch[x][0],ch[x][1]);
lzy[ch[x][0]]^=1; lzy[ch[x][1]]^=1;
lzy[x]=0;
}
int isrt(int x)
{
return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
void rotate(int x)
{
int y=fa[x],z=fa[y];
int d=(ch[y][0]==x);
if(!isrt(y))
{
if(ch[z][0]==y) ch[z][0]=x;
else ch[z][1]=x;
}
fa[y]=x; fa[ch[x][d]]=y; fa[x]=z;
ch[y][d^1]=ch[x][d]; ch[x][d]=y;
update(y); update(x);
}
void splay(int x)
{
int top=0; st[++top]=x;
for(int i=x;!isrt(i);i=fa[i])
st[++top]=fa[i];
while(top) push(st[top--]);
while(!isrt(x))
{
int y=fa[x],z=fa[y];
if(!isrt(y))
{
if((ch[z][0]==y)^(ch[y][0]==x)) rotate(x);
else rotate(y);
}
rotate(x);
}
}
void access(int x)
{
int t=0;
while(x)
{
splay(x);
ch[x][1]=t;
update(x);
t=x; x=fa[x];
}
}
void mkrt(int x)
{
access(x); splay(x);
lzy[x]^=1;
}
void match(int x,int y)
{
mkrt(x); fa[x]=y;
}
void cut(int x,int y)
{
mkrt(x);
access(y); splay(y);
fa[x]=ch[y][0]=0;
update(y);
}
int find(int x)//其实理论上用并查集应该会快挺多
{
access(x); splay(x);
while(ch[x][0]) x=ch[x][0];
return x;
}
int get(int x,int y)
{
mkrt(x);
access(y); splay(y);
return minn[y];
}
void solve()
{
int tot=0;
for(int i=1;i<=m;++i)
{
int u=E[i].u,v=E[i].v; mx=i;
if(find(u)!=find(v))
{
tot++;
match(u,i+n); match(v,i+n);
vis[i]=1;
}
else
{
int num=get(u,v); //获得u到v路径上边权最小的边
cut(E[num-n].u,num); cut(E[num-n].v,num); vis[num-n]=0;//断边
match(u,i+n); match(v,i+n); vis[i]=1;//加边
while(!vis[mi]) mi++;//维护当前树上边权最小值
}
if(tot==n-1)ans=min(ans,E[mx].dis-E[mi].dis);//更新答案
}
}
int main()
{
n=read();m=read();
for(int i=1;i<=m;++i)
{
E[i].u=read();E[i].v=read();E[i].dis=read();
if(E[i].u==E[i].v)i--,m--;//判断自环
}
sort(E+1,E+1+m,cmp);//minn表示以该节点为根的子树的边权最小的边的编号
for(int i=1;i<=m;++i)val[i+n]=E[i].dis,minn[i+n]=i+n;
for(int i=0;i<=n;++i)val[i]=1e9,minn[i]=i;//注意0号也要赋最大值
solve();
cout<<ans;
return 0;
}
原文地址:https://www.cnblogs.com/niiick/p/8900672.html