【ybt金牌导航5-4-3】【luogu P2387】魔法森林

本文介绍了一道图论问题的解决方法,该问题要求在一张无向图中找到从节点1到节点n的路径,使得路径上边的A权值最大值与B权值最大值之和最小。通过枚举最大A权值并使用LCT(链剖树)等数据结构优化,最终实现了高效求解。
摘要由CSDN通过智能技术生成

魔法森林

题目链接:ybt金牌导航5-4-3 / luogu P2387

题目大意

有一个无向图,每个边有 AB 两种权值。
要找一条从 1 到 n 的路径,使得路径上的边 A 权值最大值加 B 权值最大值最小。
如果无法到达输出 -1。

思路

首先看到两个权值的最大最小,然后每个范围都不是很大。

我们考虑枚举一个的最大值,然后把边按哪个排序,构出每个时刻的图,然后让另一个的最大值最小。
那接着问题就是如果让最大值最小了。
发现要不断的加边然后维护这个值,我们想到 LCT。
但是考虑权值在边上,而且它不是树。
我们考虑用这么一个搞法,它边有权,我们就把边看做 LCT 的点。
然后拿并查集维护点之间的连通性,如果之前不连通,就正常搞。
否则就 LCT 提取出那条链,如果它的最大值大于你的现在这条,肯定就是换(把之前的删掉再加边),否则就不管。

然后就 LCT 维护一下就可以了。

代码

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define INF 0x3f3f3f3f3f3f3f3f

using namespace std;

struct line {
	int x, y, a, b;
}a[150001];
int n, m, le[150001], KK, ans = INF;
int nowa, fa[150001], dist[150001];
int l[150001], r[150001], pl[150001];
int fath[150001];
bool lzs[150001], ch;

bool cmp(line x, line y) {//按a排序
	return x.a < y.a;
}

bool nrt(int x) {//LCT
	return l[fa[x]] == x || r[fa[x]] == x;
}

bool ls(int x) {
	return l[fa[x]] == x;
}

void up(int x) {
	pl[x] = x;//记得要清空,并算自己的
	if (l[x] && dist[pl[x]] < dist[pl[l[x]]]) pl[x] = pl[l[x]];
	if (r[x] && dist[pl[x]] < dist[pl[r[x]]]) pl[x] = pl[r[x]];
}

void downs(int x) {
	swap(l[x], r[x]);
	lzs[x] ^= 1;
}

void down(int x) {
	if (lzs[x]) {
		if (l[x]) downs(l[x]);
		if (r[x]) downs(r[x]);
		lzs[x] = 0;
	}
}

void down_line(int x) {
	if (nrt(x)) down_line(fa[x]);
	down(x);
}

void rotate(int x) {
	int y = fa[x];
	int z = fa[y];
	int b = (ls(x) ? r[x] : l[x]);
	if (z && nrt(y)) (ls(y) ? l[z] : r[z]) = x;
	if (ls(x)) r[x] = y, l[y] = b;
		else l[x] = y, r[y] = b;
	fa[x] = z;
	fa[y] = x;
	if (b) fa[b] = y;
	up(y);
}

void Splay(int x) {
	down_line(x);
	
	while (nrt(x)) {
		if (nrt(fa[x])) {
			if (ls(x) == ls(fa[x])) rotate(fa[x]);
				else rotate(x);
		}
		rotate(x);
	}
	
	up(x);
}

void access(int x) {
	int lst = 0;
	for (; x; x = fa[x]) {
		Splay(x);
		
		r[x] = lst;
		up(x);
		lst = x;
	}
}

void make_root(int x) {
	access(x);
	Splay(x);
	downs(x);
}

int find_root(int x) {
	access(x);
	Splay(x);
	while (l[x]) {
		down(x);
		x = l[x];
	}
	Splay(x);
	return x;
}

void cut(int x, int y) {
	make_root(x);
	access(y);
	Splay(y);
	l[y] = 0;
	fa[x] = 0;
	up(y);
}

void link(int x, int y) {
	make_root(x);
	if (find_root(y) != x) {
		fa[x] = y;
	}
}

int split(int x, int y) {
	make_root(x);
	access(y);
	Splay(y);
	return y;
}

int query(int x, int y) {
	x = split(x, y);
	return dist[pl[x]];
}

int find(int now) {//并查集维护连通
	if (fath[now] == now) return now;
	return fath[now] = find(fath[now]);
}

void connect(int x, int y) {
	int X = find(x);
	int Y = find(y);
	fath[X] = Y;
}

bool connect_ck(int x, int y) {
	int X = find(x);
	int Y = find(y);
	return X == Y;
}

int main() {
	scanf("%d %d", &n, &m);
	for (int i = 1; i <= m; i++) {
		scanf("%d %d %d %d", &a[i].x, &a[i].y, &a[i].a, &a[i].b);
		if (a[i].x == a[i].y) i--, m--;
	}
	
	sort(a + 1, a + m + 1, cmp);
	for (int i = 1; i <= n + m; i++)
		pl[i] = i, fath[i] = i;
	for (int i = n + 1; i <= n + m; i++)
		dist[i] = a[i - n].b;
	
	for (int i = 1; i <= 50000; i++) {//枚举最大的 a
		ch = 0;
		while (nowa < m && a[nowa + 1].a <= i) {//逐渐加边
			nowa++;
			if (connect_ck(a[nowa].x, a[nowa].y)) {//加边会出环,判断哪个更优
				int w = pl[split(a[nowa].x, a[nowa].y)];
				if (dist[w] > a[nowa].b) {//更优(把之前的边删了,再连)
					cut(a[w - n].x, w);
					cut(w, a[w - n].y);
					link(a[nowa].x, n + nowa);
					link(n + nowa, a[nowa].y);
				}
			}
			else link(a[nowa].x, n + nowa), link(n + nowa, a[nowa].y), connect(a[nowa].x, a[nowa].y);//普通连边
			
			ch = 1;
		}
		if (ch && connect_ck(1, n)) {
			ans = min(ans, i + query(1, n));
			if (nowa == m) break;
		}
	}
	
	if (ans == (int)INF) printf("-1");
		else printf("%d", ans);
	
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值