[P2387][NOI2014]魔法森林(LCT)

将所有边按a从小到大排序,加入一条新边(u,v)时,检查原先u和v是否连通,若连通则断开旧路径,新路径上最大的b和新加入的边的a相加,看能否更新答案。

既然是动态加边和删边,便考虑用lct。要查询两点间路径上的最大值,所以点和边都要建节点,并且维护最大的b和其对应的边的标号。当加入新边检查是否两点已联通时,先把b最大的边断开再连接新边,但如果已经联通且旧路径上最大值小于新边的值,就不用再试新边了。

单独判一下自环吧。至少我这种标号方式的话是必须判的。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=200010,M=100010,inf=20000000;
struct edge{
	int x,y,a1,b1;
}data[M];
struct node{
	int l,r,fa,x,m,id;
	bool rev;
}tree[N];
int n,m,ans,q[N],f[N];
inline int read(){
	int x=0,f=0;
	char ch=getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=1;ch=getchar();}
	while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
	return f?-x:x;
}
int getfa(int x){
	if(f[x]!=x)f[x]=getfa(f[x]);
	return f[x];
}
bool cmp(edge i,edge j){
	return i.a1<j.a1;
}
inline void new1(int p){
	swap(tree[p].l,tree[p].r);
	tree[p].rev=!tree[p].rev;
}
inline void pushdown(int p){
	if(tree[p].rev){
		new1(tree[p].l);new1(tree[p].r);
		tree[p].rev=false;
	}
}
inline void update(int p){
	tree[p].id=p;tree[p].m=tree[p].x;
	if(tree[tree[p].l].m>tree[p].m){
		tree[p].m=tree[tree[p].l].m;
		tree[p].id=tree[tree[p].l].id;
	}
	if(tree[tree[p].r].m>tree[p].m){
		tree[p].m=tree[tree[p].r].m;
		tree[p].id=tree[tree[p].r].id;
	}
}
inline bool nroot(int p){
	return tree[tree[p].fa].l==p||tree[tree[p].fa].r==p;
}
inline void left_rotate(int p){
	int q=tree[p].fa,r=tree[q].fa;
	tree[q].r=tree[p].l;
	if(tree[p].l)tree[tree[p].l].fa=q;
	if(nroot(q)){
		if(tree[r].l==q)tree[r].l=p;
		else tree[r].r=p;
	}
	tree[q].fa=p;tree[p].l=q;tree[p].fa=r;
	update(q);update(p);
}
inline void right_rotate(int p){
	int q=tree[p].fa,r=tree[q].fa;
	tree[q].l=tree[p].r;
	if(tree[p].r)tree[tree[p].r].fa=q;
	if(nroot(q)){
		if(tree[r].l==q)tree[r].l=p;
		else tree[r].r=p;
	}
	tree[q].fa=p;tree[p].r=q;tree[p].fa=r;
	update(q);update(p);
}
inline bool getlr(int p){
	return tree[tree[p].fa].l==p;
}
inline void rotate(int p){
	if(getlr(p))right_rotate(p);
	else left_rotate(p);
}
inline void splay(int p){
	int cnt=0;q[++cnt]=p;
	for(int i=p;nroot(i);i=tree[i].fa)q[++cnt]=tree[i].fa;
	while(cnt)pushdown(q[cnt--]);
	while(nroot(p)){
		int q=tree[p].fa;
		if(nroot(q)){
			if(getlr(q)==getlr(p))rotate(q);
			else rotate(p);
		}rotate(p);
	}
}
inline void access(int p){
	for(int x=0;p;x=p,p=tree[p].fa){splay(p);tree[p].r=x;update(p);}
}
inline void makeroot(int p){
	access(p);splay(p);new1(p);
}
inline int split(int x,int y){
	makeroot(x);access(y);splay(y);
	return tree[y].id;
}
inline void link(int x,int y){
	makeroot(x);tree[x].fa=y;
}
inline void cut(int x,int y){
	int x1=split(x,y);
	tree[y].l=tree[x].fa=0;update(y);
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;i++){
		data[i].x=read();data[i].y=read();
		data[i].a1=read();data[i].b1=read();
	}
	sort(data+1,data+m+1,cmp);
	for(int i=1;i<=n;i++)f[i]=i;
	ans=inf;bool flag=false;
	for(int i=1;i<=n;i++){
		tree[i+m].x=tree[i+m].m=0;tree[i+m].id=i+m;
		tree[i+m].l=tree[i+m].r=tree[i+m].fa=0;
		tree[i+m].rev=false;
	}
	for(int xx,yy,i=1;i<=m;i++){
		if(data[i].x==data[i].y)continue;
		tree[i].x=tree[i].m=data[i].b1;
		tree[i].l=tree[i].r=tree[i].fa=0;
		tree[i].rev=false;tree[i].id=i;
		xx=getfa(data[i].x);yy=getfa(data[i].y);
		if(xx==yy){
			int l1=split(data[i].x+m,data[i].y+m);
			if(data[i].b1<data[l1].b1){
				cut(data[l1].x+m,l1);cut(l1,data[l1].y+m);
				link(data[i].x+m,i);link(i,data[i].y+m);
			}
		}
		else{f[xx]=yy;link(data[i].x+m,i);link(i,data[i].y+m);}
		if(!flag&&getfa(1)==getfa(n))flag=true;
		if(flag){split(1+m,n+m);ans=min(ans,data[i].a1+tree[n+m].m);}
	}
	if(ans==inf)printf("-1");
	else printf("%d",ans);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值