bzoj-3669 魔法森林

141 篇文章 0 订阅
88 篇文章 0 订阅

题意:

给出一个n个点m条边的无向图,现在要从点1走到点n;

每条边有两个参数a和b,经过这条边必须分别要带不小于a和b的两种权值;

求携带的最小ab权值和,若无解输出-1;

2<=n<=50000,0<=m<=100000

题解:

挺神的一道题。。

考虑路径上最小的b的权值,只需要对b做一次最小生成树就可以了;

但是a的权值怎么办呢?

枚举!

枚举每次经过的最大的a权值,然后求b的最小生成树,更新答案;

但是为了保证最大,要按a权值升序排;

加边就是维护最小生成树啦,LCT!

正确性似乎不用太解释?

然后写一写调一调就好了;

我因为没判断弹的边和要加的边的大小关系WA了一发;

复杂度就是O(mlogm+mlogn)吧;

LCT最近刷到意识模糊。。。


代码:


#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 51000
#define which(x) (ch[fa[x]][1]==x)
using namespace std;
struct node
{
	int x,y,a,b;
}eg[N<<1];
int fa[N*3],ch[N*3][2],val[N*3],ma[N*3];
bool rt[N*3],rev[N*3];
inline int read()
{
	int x=0;char ch=getchar();
	while(ch<'0'||ch>'9')	ch=getchar();
	while(ch>='0'&&ch<='9'){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
	return x;
}
int cmp(node a,node b)
{
	if(a.a==b.a)
	return a.b<b.b;
	return a.a<b.a;
}
void Pushup(int x)
{
	ma[x]=val[x]>max(val[ma[ch[x][0]]],val[ma[ch[x][1]]])?x:
			(val[ma[ch[x][0]]]>val[ma[ch[x][1]]]?ma[ch[x][0]]:ma[ch[x][1]]);
}
void reverse(int x)
{
	swap(ch[x][0],ch[x][1]);
	rev[x]^=1;
}
void Pushdown(int x)
{
	if(rev[x])
	{
		reverse(ch[x][0]);
		reverse(ch[x][1]);
		rev[x]=0;
	}
}
void down(int x)
{
	if(!rt[x])	down(fa[x]);
	Pushdown(x);
}
void Rotate(int x)
{
	int f=fa[x];
	bool k=which(x);
	if(rt[f])	rt[f]^=rt[x]^=1;
	else	ch[fa[f]][which(f)]=x;
	ch[f][k]=ch[x][!k];
	ch[x][!k]=f;
	fa[ch[f][k]]=f;
	fa[x]=fa[f];
	fa[f]=x;
	Pushup(f);
	Pushup(x);
}
void Splay(int x)
{
	down(x);
	while(!rt[x])
	{
		int f=fa[x];
		if(rt[f])
		{
			Rotate(x);
			return ;
		}
		if(which(x)^which(f))
			Rotate(x);
		else
			Rotate(f);
		Rotate(x);
	}
}
void access(int x)
{
	int y=0;
	while(x)
	{
		Splay(x);
		rt[ch[x][1]]=1,rt[y]=0;
		ch[x][1]=y;
		Pushup(x);
		y=x,x=fa[x];
	}
}
void Mtr(int x)
{
	access(x);
	Splay(x);
	reverse(x);
}
bool judge(int x,int y)
{
	Mtr(x);
	access(y);
	Splay(x);
	while(!rt[y])
		y=fa[y];
	return x==y;
}
void Link(int x,int y)
{
	Mtr(x);
	fa[x]=y;
}
void Cut(int t,int x,int y)
{
	Mtr(t);
	access(x);
	Splay(t);
	rt[ch[t][1]]=1;
	fa[ch[t][1]]=0;
	ch[t][1]=0;
	access(y);
	Splay(t);
	rt[ch[t][1]]=1;
	fa[ch[t][1]]=0;
	ch[t][1]=0;
}
int main()
{
	int n,m,i,j,k,x,y,ans;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++)
	{
		eg[i].x=read(),eg[i].y=read(),eg[i].a=read(),eg[i].b=read();
	}
	sort(eg+1,eg+1+m,cmp);
	for(i=1;i<=n;i++)
		rt[i]=1,ma[i]=i;
	for(i=1,ans=0x3f3f3f3f;i<=m;i++)
	{
		x=eg[i].x,y=eg[i].y;
		if(x==y)	continue;
		rt[i+n]=1,val[i+n]=eg[i].b,ma[i+n]=i+n;
		if(judge(x,y))
		{
			if(val[ma[x]]>eg[i].b)
				Cut(ma[x],x,y),Link(i+n,x),Link(i+n,y);
		}
		else
		Link(i+n,x),Link(i+n,y);
		if(judge(1,n))
			ans=min(ans,eg[i].a+val[ma[1]]);
	}
	if(ans==0x3f3f3f3f)
		puts("-1");
	else
		printf("%d\n",ans);
	return 0;
}



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值