魔法森林
题目链接: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;
}