【NOI 2014】魔法森林

【题目】

传送门

题目描述:

为了得到书法大家的真传,小 E 同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含 n n n 个节点 m m m 条边的无向图,节点标号为 1 1 1 2 2 2 3 3 3 … … n n n,边标号为 1 1 1 2 2 2 3 3 3 … … m m m 。初始时小 E 同学在 1 1 1 号节点,隐士则住在 n n n 号节点。小 E 需要通过这一片魔法森林,才能够拜访到隐士。

魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在 1 1 1 号节点住着两种守护精灵:A 型守护精灵 B 型守护精灵。小 E 可以借助它们的力量,达到自己的目的。

只要小 E 带上足够多的守护精灵,妖怪们就不会发起攻击了。具体来说,无向图中的每一条边 e i e_i ei 包含两个权值 a i a_i ai b i b_i bi 。若身上携带的 A 型守护精灵个数不少于 a i a_i ai,且 B 型守护精灵个数不少于 b i b_i bi,这条边上的妖怪就不会对通过这条边的人发起攻击。当且仅当通过这片魔法森林的过程中没有任意一条边的妖怪向小 E 发起攻击,他才能成功找到隐士。

由于携带守护精灵是一件非常麻烦的事,小 E 想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A 型守护精灵的个数与 B 型守护精灵的个数之和。

输入格式:

输入第 1 1 1 行包含两个整数 n n n m m m,表示无向图共有 n n n 个节点, m m m 条边。

接下来 m m m 行,第 i + 1 i+1 i+1 行包含 4 4 4 个正整数 x i x_i xi y i y_i yi a i a_i ai b i b_i bi,描述第 i i i 条无向边。其中 x i x_i xi y i y_i yi 为该边两个端点的标号, a i a_i ai b i b_i bi 的含义如题所述。

注意数据中可能包含重边与自环。

输出格式:

输出一行一个整数:

如果小 E 可以成功拜访到隐士,输出小 E 最少需要携带的守护精灵的总个数;

如果无论如何小 E 都无法拜访到隐士,输出 “-1”(不含引号)。

样例数据:

【样例 1 1 1

输入
4 5
1 2 19 1
2 3 8 12
2 4 12 15
1 3 17 8
3 4 1 17

输出
32

【样例 2 2 2

输入
3 1
1 2 1 1

输出
-1

备注:

【样例 1 1 1 说明】
在这里插入图片描述
如果小 E 走路径 1 → 2 → 4 1→2→4 124,需要携带 19 + 15 = 34 19+15=34 19+15=34 个守护精灵;
如果小 E 走路径 1 → 3 → 4 1→3→4 134,需要携带 17 + 17 = 34 17+17=34 17+17=34 个守护精灵;
如果小 E 走路径 1 → 2 → 3 → 4 1→2→3→4 1234,需要携带 19 + 17 = 36 19+17=36 19+17=36 个守护精灵;
如果小 E 走路径 1 → 3 → 2 → 4 1→3→2→4 1324,需要携带 17 + 15 = 32 17+15=32 17+15=32 个守护精灵。
综上所述,小 E 最少需要携带 32 32 32 个守护精灵。

【样例 2 2 2 说明】

E 无法从 1 1 1 号节点到达 3 3 3 号节点,故输出 − 1 -1 1

【数据范围】
在这里插入图片描述


【分析】

先说一下暴力怎么做吧。

我们先按照 a i a_i ai 从小到大排序,一条条加边,每次对于 b i b_i bi 做一次最小生成树,选取生成树中最大的边与此时的 a i a_i ai 相加就是当前的答案,取个最小值即可。

这样做显然是 n 2 n^2 n2 的,考虑怎么优化这一过程。

每次加入一条边之后,如果重新做最小生成树,那上一次的信息就浪费掉了,就浪费了时间。于是我们考虑怎么利用上一次的最小生成树来确定这次的最小生成树。

当我们在一个最小生成树中加入一条边时,显然就会产生一个环,我们只需要删掉这个环上最大的边就可以了。

也就是说,我们现在要维护一种数据结构,它能支持:

  1. 连接两个点。
  2. 删除两个点之间的边。
  3. 查询两个点之间的路径中的边权最大值以及最大边权所在位置

保证这是一颗树。

因此我们用 LCT 来维护。

由于边权问题不好维护,我们化边为点,具体做法就是对于边 ( u , v ) (u,v) (u,v),新建一个点 x x x,它的点权就是 ( u , v ) (u,v) (u,v) 的边权,然后 l i n k ( x , u ) link(x,u) link(x,u) l i n k ( x , v ) link(x,v) link(x,v),这样询问就可以转化为点权最大值了。


【代码】

#include<stack>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 200005
using namespace std;
stack<int>stk;
int n,m,fa[N],son[N][2],mark[N],val[N],Max[N],id[N];
struct edge{int u,v,a,b;}e[N];
bool operator<(const edge &p,const edge &q){return p.a<q.a;}
int Get(int x)
{
	return x==son[fa[x]][1];
}
bool Isroot(int x)
{
	if(!fa[x])  return true;
	return (son[fa[x]][0]!=x)&&(son[fa[x]][1]!=x);
}
void Pushup(int x)
{
	int l=son[x][0],r=son[x][1];
	if(val[x]>Max[l]&&val[x]>Max[r])  Max[x]=val[x],id[x]=x;
	else  if(Max[l]>Max[r])  Max[x]=Max[l],id[x]=id[l];
	else  Max[x]=Max[r],id[x]=id[r];
}
void Pushdown(int x)
{
	if(!mark[x])  return;
	if(son[x][0])  mark[son[x][0]]^=1;
	if(son[x][1])  mark[son[x][1]]^=1;
	swap(son[x][0],son[x][1]),mark[x]=0;
}
void Rotate(int x)
{
	int y=fa[x],z=fa[y];
	int k=Get(x),l=son[x][k^1];
	son[y][k]=l;fa[l]=(l?y:0);
	if(!Isroot(y))  son[z][Get(y)]=x;fa[x]=z;
	son[x][k^1]=y,fa[y]=x;
	Pushup(y),Pushup(x);
}
void Splay(int x)
{
	stk.push(x);
	for(int i=x;!Isroot(i);i=fa[i])  stk.push(fa[i]);
	while(!stk.empty())  Pushdown(stk.top()),stk.pop();
	while(!Isroot(x))
	{
		int y=fa[x];
		if(!Isroot(y))  Rotate(Get(x)==Get(y)?y:x);
		Rotate(x);
	}
}
void Access(int x)
{
	int i;
	for(i=0;x;x=fa[i=x])
	  Splay(x),son[x][1]=i,Pushup(x);
}
int Findroot(int x)
{
	Access(x),Splay(x);
	while(Pushdown(x),son[x][0])  x=son[x][0];
	Splay(x);return x;
}
void Makeroot(int x)
{
	Access(x);
	Splay(x);
	mark[x]^=1;
}
void Select(int x,int y)
{
	Makeroot(y);
	Access(x),Splay(x);
}
void Link(int x,int y)
{
	Makeroot(x);
	fa[x]=y;
}
void Cut(int x,int y)
{
	Makeroot(x);
	Access(y);Splay(y);
	son[y][0]=fa[x]=0;
	Pushup(y);
}
void FindMax(int x,int y,int &num,int &pos)
{
	Select(x,y);
	num=Max[x],pos=id[x];
}
int main()
{
	int i,ans=100001;
	scanf("%d%d",&n,&m);
	for(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);
	for(i=1;i<=n+m;++i)  id[i]=i;
	for(i=1;i<=m;++i)  Max[n+i]=val[n+i]=e[i].b;
	for(i=1;i<=m;++i)
	{
		int num,pos;
		int x=e[i].u,y=e[i].v;
		if(e[i].a>=ans)  break;
		if(Findroot(x)!=Findroot(y))
		  Link(n+i,x),Link(n+i,y);
		else
		{
			FindMax(x,y,num,pos);
			if(num>e[i].b)
			{
				Cut(pos,e[pos-n].u);
				Cut(pos,e[pos-n].v);
				Link(n+i,x);
				Link(n+i,y);
			}
		}
		if(Findroot(1)==Findroot(n))
		{
			FindMax(1,n,num,pos);
			ans=min(ans,e[i].a+num);
		}
	}
	printf("%d",(ans==100001)?-1:ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值