#3. 【NOI2014】魔法森林

#3. 【NOI2014】魔法森林

序言

U O J UOJ UOJ果然是神仙 O J OJ OJ
里面个个都是人才,说话还好听
题目的 h a c k hack hack数据还可以 h a c k hack hack掉网上一大片的题解…

题目描述

为了得到书法大家的真传,小 E E E同学下定决心去拜访住在魔法森林中的隐士。魔法森林可以被看成一个包含个 N N N节点 M M M条边的无向图,节点标号为 1 … n 1…n 1n,边标号为 1 … m 1…m 1m。初始时小 E E E同学在 1 1 1 号节点,隐士则住在 n n n 号节点。小 E E E需要通过这一片魔法森林,才能够拜访到隐士。
魔法森林中居住了一些妖怪。每当有人经过一条边的时候,这条边上的妖怪就会对其发起攻击。幸运的是,在 1 1 1 号节点住着两种守护精灵: A A A型守护精灵与 B B B型守护精灵。小 E E 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 E发起攻击,他才能成功找到隐士。
由于携带守护精灵是一件非常麻烦的事,小E想要知道,要能够成功拜访到隐士,最少需要携带守护精灵的总个数。守护精灵的总个数为 A A A型守护精灵的个数与 B B B型守护精灵的个数之和。


题目大意:
1 1 1走到 n n n, m m m条边,如何走能使 a a a b b b的和最大值最小.

解析

首先,这是一个最小生成树的题目.
其次,这应该是一个动态最小生成树的题目.
(如果你学过用LCT做最小生成树,这个题就非常的水)
我们将 a i a_i ai排序后,按边取,可以保证 a i a_i ai是最小的.
在按 a i a_i ai大小取边的时候,同时更新路上 b i b_i bi的最大值.
当我们取到一个环的时候怎么办?
列如这样:
1
我们就要对新加入的边上 b i b_i bi的值,和维护的链上 b i b_i bi的最大值进行比较.

  1. 如果链上的值大,那么我们把链上最大的那条边 c u t cut cut,如何把新边连上.
  2. 如果新边大,我们便直接跳过.

如此往复,直到 1 1 1~ n n n这条路连通.
连通后,我们加入另一个操作:
判断,加入的这条边对于答案的影响. a a a大并不代表 b b b也大,因为问的是 a + b a+b a+b的和.
直到遍历完所有边.

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;

const int INF = 1e9;
const int MAXN = 2e5 + 10;

struct node {
	int x, y, a, b;
	bool operator < (const node &rhs) const {
		return a < rhs.a;
	}
}edge[MAXN];

struct vec {
	int fa, son[2];
	bool rev;
	int maxx, id;
}p[MAXN];
int n;

int isroot(int x) {
	return p[p[x].fa].son[0] != x && p[p[x].fa].son[1] != x;
}
void pushup(int x) {
	int A = p[x].id, B = p[p[x].son[0]].maxx, C = p[p[x].son[1]].maxx;
	if (edge[A].b >= edge[B].b&&edge[A].b >= edge[C].b)
		p[x].maxx = A;
	else if (edge[B].b >= edge[C].b)
		p[x].maxx = B;
	else
		p[x].maxx = C;
}
void pushdown(int x) {
	if (p[x].rev) {
		int l = p[x].son[0], r = p[x].son[1];
		swap(p[l].son[0], p[l].son[1]), p[l].rev ^= 1;
		swap(p[r].son[0], p[r].son[1]), p[r].rev ^= 1;
		p[x].rev ^= 1;
	}
}
int get(int x) {
	return p[p[x].fa].son[1] == x;
}
void rotate(int x) {
	int y = p[x].fa, t = p[y].fa, f = get(x), k = p[x].son[f ^ 1];
	p[p[k].fa = y].son[f] = p[x].son[f ^ 1];
	p[x].fa = t;
	if (!isroot(y))
		p[t].son[get(y)] = x;
	p[p[y].fa = x].son[f ^ 1] = y;
	pushup(y);
}
int top, stk[MAXN];
void splay(int x) {
	stk[top = 1] = x;
	for (int i = x; !isroot(i); i = p[i].fa)
		stk[++top] = p[i].fa;
	while (top)
		pushdown(stk[top--]);
	for (; !isroot(x); rotate(x))
		if (!isroot(p[x].fa))
			rotate(get(x) ^ get(p[x].fa) ? x : p[x].fa);
	pushup(x);
}
void access(int x) {
	for (int i = 0; x; x = p[i = x].fa)
		splay(x), p[x].son[1] = i, pushup(x);
}
void makeroot(int x) {
	access(x);
	splay(x);
	p[x].rev ^= 1;
	swap(p[x].son[0], p[x].son[1]);
}
int findroot(int x) {
	access(x); splay(x);
	while (p[x].son[0])
		pushdown(x), x = p[x].son[0];
	splay(x);
	return x;
}
void split(int x, int y) {
	makeroot(x);
	access(y);
	splay(y);
}
int link(int x, int y) {
	makeroot(x);
	if (findroot(y) == x)
		return false;
	p[x].fa = y;
	return true;
}
void Cut(int x, int y) {
	split(x, y);
	if (p[y].son[0] == x)
		p[x].fa = p[y].son[0] = 0;
}

void Addedge(int id) {
	int x = edge[id].x, y = edge[id].y;
	if (findroot(x) != findroot(y))
		link(x, id + n), link(id + n, y);
	else {
		split(x, y);
		if (edge[p[y].maxx].b > edge[id].b) {
			int tmp = p[y].maxx;
			Cut(edge[tmp].x, tmp + n), Cut(tmp + n, edge[tmp].y);
			link(edge[id].x, id + n), link(id + n, edge[id].y);
		}
	}
}
int main() {
	int m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++)
		cin >> edge[i].x >> edge[i].y >> edge[i].a >> edge[i].b;

	sort(edge + 1, edge + m + 1);
	for (int i = 1; i <= m; i++)
		p[i + n].maxx = p[i + n].id = i;

	int ans = INF;
	for (int i = 1; i <= m; i++) {
		Addedge(i);
		while (edge[i].a == edge[i + 1].a)
			Addedge(++i);
		if (findroot(1) == findroot(n)) {
			split(1, n);
			ans = min(ans, edge[p[n].maxx].b + edge[i].a);
		}
	}

	printf("%d\n", ans == INF ? -1 : ans);

	//system("pause");
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值