【题目链接】
【思路要点】
- 可参考 官方题解 。
- 以下为笔者个人的见解,方便起见,下称矿工为老鼠,金矿为洞。
- 我们可以对洞的权值加上深度,老鼠的权值减去深度,从而不需要考虑树的边权。
- 考虑新加一只老鼠带来的影响,可能的结果有如下三种:
( 1 ) (1) (1) 、与一个尚未匹配的洞一起加入当前已经匹配的集合。
( 2 ) (2) (2) 、取代当前已经匹配的集合中的一只老鼠。
( 3 ) (3) (3) 、没有被加入匹配集合。- 这里我们考虑的匹配集合不需要考虑具体的匹配方案,而只需要保证存在至少一组可能的匹配方案。
- 当一个权为 A A A 的老鼠和一个权为 B B B 的洞匹配时,我们可以认为在原来的老鼠处出现了一个权为 − A -A −A 的洞,在原来的洞处出现了一个权为 − B -B −B 的老鼠。
- 可以发现,这样看待问题时,上述三种可能的结果本质上是统一的,即让当前老鼠找到一个能够匹配的权最大的洞匹配之,若这样做不能使答案增大,则放弃此次匹配。注意到匹配成功后新产生的洞是不会再次成功匹配的,否则之前的答案一定不是最优的。
- 因此,我们现在只需要考虑 “找到一个能够匹配的权最大的洞” 的过程即可。
- 对于老鼠出现的节点 x x x , x x x 子树内的洞一定都能匹配,并且若 x x x 的父边被向下经过至少一次,那么向上经过这条边就可以看做转而调整经过这条边的另一个老鼠所匹配的洞。因此沿 x x x 的父边一直向上,直到到达的点的父边没有被向下经过过,该点子树内的所有洞即为能够匹配的范围。
- 对于加入一个洞的过程,同样可以类似地考虑,即考虑只经过向上和被向下经过过的向下的边能够到达的老鼠。
- 朴素地实现该算法,其时间复杂度为 O ( N × M + M L o g M ) O(N\times M+MLogM) O(N×M+MLogM) ,可以参考下列代码中 355 355 355 行后的内容。
- 考虑采用树链剖分优化 “找到一个能够匹配的权最大的洞 / 老鼠” 的 过程。
- 找洞的过程是较为容易的,只需在线段树上二分找到走到最上方的点即可。
- 找老鼠的过程可以通过维护每个点所有轻儿子的子树中能够到达该点的老鼠的权值完成。
- 时间复杂度 O ( N + Q L o g 2 N ) O(N+QLog^2N) O(N+QLog2N) 。
【代码】
// Program Till Line 355 #include<bits/stdc++.h> using namespace std; const int MAXN = 2e5 + 5; const int INF = 1e9; typedef long long ll; typedef long double ld; typedef unsigned long long ull; template <typename T> void chkmax(T &x, T y) { x = max(x, y); } template <typename T> void chkmin(T &x, T y) { x = min(x, y); } template <typename T> void read(T &x) { x = 0; int f = 1; char c = getchar(); for (; !isdigit(c); c = getchar()) if (c == '-') f = -f; for (; isdigit(c); c = getchar()) x = x * 10 + c - '0'; x *= f; } template <typename T> void write(T x) { if (x < 0) x = -x, putchar('-'); if (x > 9) write(x / 10); putchar(x % 10 + '0'); } template <typename T> void writeln(T x) { write(x); puts(""); } struct SegmentTree { struct Node { int lc, rc; int tag, Minf; pair <int, int> Maxh; pair <int, int> Maxm; } a[MAXN * 2]; int n, root, size; void update(int root) { a[root].Minf = min(a[a[root].lc].Minf, a[a[root].rc].Minf); a[root].Maxh = max(a[a[root].lc].Maxh, a[a[root].rc].Maxh); a[root].Maxm = max(a[a[root].lc].Maxm, a[a[root].rc].Maxm); } void build(int &root, int l, int r) { root = ++size; if (l == r) { a[root].Maxh = make_pair(-INF, 0); a[root].Maxm = make_pair(-INF, 0); a[root].Minf = 0; return; } int mid = (l + r) / 2; build(a[root].lc, l, mid); build(a[root].rc, mid + 1, r); update(root); } void init(int x) { n = x; root = size = 0; build(root, 1, n); } void pushdown(int root) { if (a[root].tag) { a[a[root].lc].tag += a[root].tag; a[a[root].lc].Minf += a[root].tag; a[a[root].rc].tag += a[root].tag; a[a[root].rc].Minf += a[root].tag; a[root].tag = 0; } } void modifyf(int root, int l, int r, int ql, int qr, int d) { if (l == ql && r == qr) { a[root].Minf += d; a[root]