Luogu2387 [NOI2014]魔法森林
Description
给你一个图,图上每条边都有两个权值\(a, b\)。经过一条边需要自身的属性\(x, y\)满足\(a \leq x, b \leq y\)。你现在要从\(1\)号点走到\(n\)号点,问你最小需要的自身属性\(x, y\)的和
Solution
我们可以考虑把边按\(a_i\)先进行排序,然后依次加入到图中。因为当边的\(a_i\)的最大值一定时,我们只需要最小化\(b_i\)即可。所以我们直接把\(b_i\)看成权值,然后用LCT去维护最小生成树就可以了
在用LCT维护的时候,我们可以把每条边都对应建一个点,然后这个点的点权即为对应边的边权,其它点的点权为\(0\)。我们每添加一条边,需要先判断是否已经联通。若已联通,并且链上的最大值比当前添加的边的边权要大,则用当前边去替换
Code
#include <bits/stdc++.h>
using namespace std;
#define fst first
#define snd second
#define mp make_pair
#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)
typedef long long LL;
typedef pair<int, int> pii;
template<typename T> inline bool chkmax(T &a, const T &b) { return a < b ? a = b, 1 : 0; }
template<typename T> inline bool chkmin(T &a, const T &b) { return a > b ? a = b, 1 : 0; }
inline int read() {
int sum = 0, fg = 1; char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
return fg * sum;
}
const int maxn = 5e4 + 10;
const int maxm = 1e5 + 10;
const int inf = 5e4;
int n, m;
struct edge {
int x, y, a, b;
void input() { x = read(), y = read(), a = read(), b = read(); }
bool operator < (const edge &t) const { return a < t.a; }
}e[maxm];
namespace LCT {
struct node {
int son[2], f;
pii v, s; bool rev;
}A[maxn + maxm];
#define ls(x) A[x].son[0]
#define rs(x) A[x].son[1]
#define fa(x) A[x].f
void push_up(int x) { A[x].s = max(A[x].v, max(A[ls(x)].s, A[rs(x)].s)); }
void push_down(int x) {
if (A[x].rev) {
swap(ls(x), rs(x));
A[ls(x)].rev ^= 1, A[rs(x)].rev ^= 1, A[x].rev = 0;
}
}
bool chkrt(int x) { return x != ls(fa(x)) && x != rs(fa(x)); }
bool chk(int x) { return x == rs(fa(x)); }
void link(int x, int y, int f) { fa(x) = y, A[y].son[f] = x; }
void rotate(int x) {
int f = fa(x), dx = chk(x), df = chk(f);
link(A[x].son[dx ^ 1], f, dx);
if (!chkrt(f)) link(x, fa(f), df); else fa(x) = fa(f);
link(f, x, dx ^ 1);
push_up(f), push_up(x);
}
void splay(int x) {
int _x = x;
static int S[maxn]; S[0] = 0;
while (!chkrt(x)) S[++S[0]] = x, x = fa(x);
S[++S[0]] = x, swap(x, _x);
for (int i = S[0]; i; i--) push_down(S[i]);
while (!chkrt(x)) {
int f = fa(x), dx = chk(x), df = chk(f);
if (chkrt(f)) rotate(x);
else if (dx == df) rotate(f), rotate(x);
else rotate(x), rotate(x);
}
}
void access(int x) { int _x = x; for (int lst = 0; x; lst = x, x = fa(x)) splay(x), rs(x) = lst, push_up(x); splay(_x); }
void mkrt(int x) { access(x), A[x].rev ^= 1; }
int getrt(int x) { access(x); while (ls(x)) push_down(x = ls(x)); return x; }
void split(int x, int y) { mkrt(x), access(y); }
void Link(int x, int y) { mkrt(x), fa(x) = y; }
void Cut(int x, int y) { split(x, y), fa(x) = 0, ls(y) = 0, push_up(y); }
pii get_max(int x, int y) { split(x, y); return A[y].s; }
void add(int x, int y, int v, int p1) {
if (getrt(x) == getrt(y)) {
pii res = get_max(x, y);
if (res.fst <= v) return;
int p2 = res.snd;
Cut(e[p2].x, p2 + n), Cut(p2 + n, e[p2].y);
}
Link(x, p1 + n), Link(p1 + n, y);
splay(p1 + n), A[p1 + n].v = mp(v, p1), push_up(p1 + n);
}
int check() {
if (getrt(1) == getrt(n)) return get_max(1, n).fst;
return inf << 2;
}
}
int main() {
#ifdef xunzhen
freopen("tree.in", "r", stdin);
freopen("tree.out", "w", stdout);
#endif
n = read(), m = read();
for (int i = 1; i <= m; i++) e[i].input();
sort(e + 1, e + m + 1);
int ans = inf << 2;
for (int i = 1, j = 1; i <= inf; i++) {
while (j <= m && e[j].a <= i) LCT::add(e[j].x, e[j].y, e[j].b, j), ++j;
chkmin(ans, i + LCT::check());
}
printf("%d\n", ans < (inf << 2) ? ans : -1);
return 0;
}
19-2-11upd
这题\(Splay\)函数里的\(S\)数组其实最好要开\(maxn + maxm\) 其实也无所谓