【LCT】[NOI2014]魔法森林

题目

在这里插入图片描述

思路

最大值最小?还跟路径有关?当然是维护最小生成树啦
只有一个值很简单:暴力加边并查集判断连通性,当1,n第一次连通的时候答案一定是最小的
可是现在有两个值怎么办呢
考虑按a排序,维护以b为关键字的最小生成树,答案就是1,n路径上最大值+当前的a
然而b是无序的,所以维护这个最小生成树需要找到环上最大的边断开然后加入新边,所以我们要用LCT

那么问题来了,怎么维护边权?
在每条边中间加一个点代表边权就行了

代码

#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f,N=2e5+77;
int mx[N],id[N],ch[N][2],rev[N],fa[N],n,m,w[N],Q[N],ans=inf;
struct E
{
	int u,v,a,b;
}e[N];
bool cmp(E x,E y)
{
	return x.a<y.a||(x.a==y.a&&x.b<y.b);
}
bool isroot(int x)
{
	return ch[fa[x]][0]!=x&&ch[fa[x]][1]!=x;
}
bool get(int x)
{
	return ch[fa[x]][1]==x;
}
void update(int x)
{
	id[x]=x; mx[x]=w[x];
	if(ch[x][0]&&mx[ch[x][0]]>mx[x]) mx[x]=mx[ch[x][0]],id[x]=id[ch[x][0]];
	if(ch[x][1]&&mx[ch[x][1]]>mx[x]) mx[x]=mx[ch[x][1]],id[x]=id[ch[x][1]];
}
void pushdown(int x)
{
	if(!rev[x]) return;
	rev[ch[x][0]]^=1; rev[ch[x][1]]^=1;
	swap(ch[x][0],ch[x][1]);
	rev[x]=0;
}
void rotate(int x)
{
	int y=fa[x],z=fa[y],l=get(x),r=l^1;
	if(!isroot(y)) ch[z][get(y)]=x;
	fa[x]=z; fa[y]=x; fa[ch[x][r]]=y; ch[y][l]=ch[x][r]; ch[x][r]=y;
	update(x); update(y);
}
void splay(int x)
{
	int top=0;
	Q[++top]=x;
	for(int i=x; !isroot(i); i=fa[i]) Q[++top]=fa[i];
	while(top) pushdown(Q[top--]);
	while(!isroot(x))
	{
		int y=fa[x],z=fa[y],d1=ch[y][1]==x,d2=ch[z][1]==y;
		if(!isroot(y))
		{
			if(d1==d2) rotate(y),rotate(x); else rotate(x),rotate(x);
		}
		else rotate(x);
	}
	update(x);
}
void access(int x)
{
	for(int t=0; x; t=x,x=fa[x])
	{
		splay(x),ch[x][1]=t,update(x);
		if(t) fa[t]=x;
	}
}
void makeroot(int x)
{
	access(x); splay(x); rev[x]^=1;
}
void split(int x,int y)
{
	makeroot(x); access(y); splay(y);
}
void link(int x,int y)
{
	makeroot(x); fa[x]=y;
}
void cut(int x,int y)
{
	split(x,y); ch[y][0]=fa[x]=0; update(y);
}
int findroot(int x)
{
	access(x); splay(x);
	while(ch[x][0]) pushdown(x),x=ch[x][0];
	return x;
}
bool check(int x,int y)
{
	makeroot(x);
	return findroot(y)==x;
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1; i<=m; i++) scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].a,&e[i].b);
	sort(e+1,e+m+1,cmp);
	for(int i=1; i<=m; i++)
	{
		w[i+n]=e[i].b;
		if(e[i].u==e[i].v) continue;
		if(!check(e[i].u,e[i].v)) link(e[i].u,i+n),link(i+n,e[i].v);
		else
		{
			split(e[i].u,e[i].v);
			int yjy=id[e[i].v],aii=mx[e[i].v];
			if(aii<=e[i].b) continue;
			splay(yjy);
			fa[ch[yjy][0]]=fa[ch[yjy][1]]=0;
			link(e[i].u,i+n),link(i+n,e[i].v);
		}
		if(check(1,n))
		{
			split(1,n); ans=min(ans,mx[n]+e[i].a);
		}
	}
	if(ans==inf) printf("-1");else printf("%d",ans);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值