「NOI2014」 魔法森林 - 动态树LCT

题目描述

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

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

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

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

输入格式

第1行包含两个整数 N , M N,M N,M,表示无向图共有 N N N个节点, M M M条边。 接下来 M M M行,第行包含 4 4 4个正整数 X i , Y i , A i , B i Xi_,Y_i,A_i,B_i Xi,Yi,Ai,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

分析

如果没有 B B B型守护精灵,那么就是求 1 1 1~ N N N的所有路径中最大边权的最小值。很容易想到用最小生成树解决。现在一条边有两个权值,同样按照Kruskal算法的思路,固定边的一种权值 A i A_i Ai,将其按从小到大排序,然后就可以发现,答案中 B i B_i Bi就一定在以 B i B_i Bi为权值的最小生成树上,于是就可以用LCT维护 B B B的最小生成树。当 1 1 1 N N N连通时就更新答案。对于自环与重边,可以不处理,LCT会自动过滤。似乎还有一种用Spfa的方法,供大家思考。 (LCT也没那么长,压行后百行都不到)

代码

#include <algorithm>
#include <climits>
#include <cstdio>
using namespace std;
const int N=500005,M=100005;
const int V=N+M,INF=INT_MAX>>1;
typedef long long LL;
struct Edge {int u,v,ca,cb;}e[M];
int n,m,fa[N],ans;
bool cmp(Edge a,Edge b) {return a.ca<b.ca;}
int getf(int x) {return fa[x]==x?x:fa[x]=getf(fa[x]);}
int c[V][2],f[V];
int mx[V],v[V],rv[V],st[V];
void init(int x,int p) {mx[x]=v[x]=p;}
bool nroot(int x) {return c[f[x]][0]==x||c[f[x]][1]==x;}
void makerv(int x) {rv[x]^=1;swap(c[x][0],c[x][1]);}
void pushup(int x) {
	mx[x]=v[x];
	if (e[mx[c[x][0]]].cb>e[mx[x]].cb) mx[x]=mx[c[x][0]];
	if (e[mx[c[x][1]]].cb>e[mx[x]].cb) mx[x]=mx[c[x][1]];
}
void pushdown(int x) {
	if (!x||!rv[x]) return;
	if (c[x][0]) makerv(c[x][0]);
	if (c[x][1]) makerv(c[x][1]);
	rv[x]=0;
}
void pushtg(int x) {
	int top=0;
	st[++top]=x;
	while (f[x]) st[++top]=(x=f[x]);
	while (top) pushdown(st[top--]);
}
void rotate(int x) {
	int y=f[x],z=f[y];
	int k=c[y][1]==x;
	if (nroot(y)) c[z][c[z][1]==y]=x;
	f[x]=z;f[y]=x;
	if (c[x][k^1]) f[c[x][k^1]]=y;
	c[y][k]=c[x][k^1];c[x][k^1]=y;
	pushup(y);pushup(x);
}
void splay(int x) {
	pushtg(x);
	for (int y,z;y=f[x],z=f[y],nroot(x);rotate(x))
		if (nroot(y)) rotate((c[y][0]==x)^(c[z][0]==y)?x:y);
	pushup(x);
}
void access(int x) {for (int y=0;x;splay(x),c[x][1]=y,pushup(x),x=f[y=x]);}
void makert(int x) {access(x);splay(x);makerv(x);}
void split(int x,int y) {makert(x);access(y);splay(y);}
void link(int x,int y) {makert(x);f[x]=y;pushup(y);}
void cut(int x,int y) {split(x,y);f[x]=c[y][0]=0;pushup(y);}
int main() {
	scanf("%d%d",&n,&m);
	for (int i=1;i<=m;i++) {
		int u,v,a,b;
		scanf("%d%d%d%d",&u,&v,&a,&b);
		e[i]=(Edge){u,v,a,b};
	}
	sort(e+1,e+m+1,cmp);
	for (int i=1;i<=n;i++) init(i,0),fa[i]=i;
	for (int i=n+1;i<=n+m;i++) init(i,i-n);
	ans=INF;
	for (int i=1;i<=m;i++) {
		int u=e[i].u,v=e[i].v;
		int fu=getf(u),fv=getf(v);
		if (fu!=fv) link(u,i+n),link(i+n,v),fa[fu]=fv;
		else {
			split(u,v);
			int num=mx[v];
			if (e[num].cb>e[i].cb) {
				cut(e[num].u,num+n);cut(num+n,e[num].v);
				link(u,i+n);link(i+n,v);
			}
		}
		if (getf(1)==getf(n)) {
			split(1,n);
			ans=min(ans,e[i].ca+e[mx[n]].cb);
		}
	}
	if (ans==INF) printf("-1");
	else printf("%d",ans);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
信息学奥赛是一项重要的竞赛活动,旨在培养学生的计算机科学和信息技术能力。省选是指在全省范围内举办的预赛,参赛选手需要通过省选才能晋级到更高层次的比赛。在省选中发挥出色的选手将有机会代表本省参加全国信息学奥林匹克竞赛(NOI)。 而noi_pdf-2020.12.29.rar 是一个压缩文件的命名,其中包含了一份关于NOI的PDF文档。这个文件可能包含了有关NOI的相关资料,例如竞赛规则、题目类型、考试要求等等。通过研究这个文件,选手可以更好地准备信息学奥赛,提高竞赛成绩。 对于想要参加信息学奥赛的同学们来说,可以利用这份PDF文档来深入了解NOI的要求和考试内容。首先,可以仔细阅读竞赛规则,了解比赛的时间、地点、参赛资格等重要信息。其次,可以通过研究题目类型和考试要求,明确自己需要学习和复习的内容,制定合理的备考计划。此外,可以通过查阅往年的竞赛题目和解答,进行练习和模拟考试,提高解题能力和应变能力。 综上所述,信息学奥赛省选和noi_pdf-2020.12.29.rar对于想要参加信息学奥赛的同学们来说都有重要的意义。省选是选拔出优秀选手的一个重要阶段,而noi_pdf-2020.12.29.rar则提供了有关NOI的重要资料,帮助选手更好地准备竞赛。希望通过努力学习和准备,同学们可以在比赛中取得优异成绩,提升自己的计算机科学和信息技术能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值