题解:P17851 「RiOI-(-2)」houran 和芒果 / [ROI 2026 Day1] 泰莫利亚的调查 加强版
Register_int · · 题解
保证开到了 std 的 1.5 倍。
直接 dp。设
- 放弃前面选的所有东西,在当前子树选链。
- 或者在前面的基础上在当前子树选链。
也就是说,当
凸性并不显然,棘手的主要是第二个转移比较难以想象,不过可以发现:如果假设
- 将
dp_{u,0} 加上w 。 - 将
dp_u,dp_v 进行(\max,+) 卷积合并。 - 再将
dp_{u,0} 减去w 。
由于合并过程中
:::info[这部分的代码实现]
#include <bits/stdc++.h>
using namespace std;
char buf[1 << 20], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
inline
int read() {
int x = 0; char c = getchar();
for (; c < '0' || c > '9'; c = getchar());
for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = getchar());
return x;
}
typedef long long ll;
const int MAXN = 2e5 + 10;
struct node { int l, r; ll x; } t[MAXN]; int rt[MAXN], tot;
int merge(int u, int v) {
if (!u || !v) return u | v;
if (t[u].x < t[v].x) swap(u, v);
t[u].r = merge(t[u].r, v), swap(t[u].l, t[u].r);
return u;
}
vector<pair<int, int>> g[MAXN];
void dfs(int u, int f = 0) {
sort(g[u].begin(), g[u].end(),
[](const auto &x, const auto &y) { return x.second > y.second; });
for (auto _ : g[u]) {
int v = _.first, w = _.second;
if (v == f) continue; dfs(v, u);
if (rt[u]) t[rt[u]].x -= w;
rt[u] = merge(rt[u], rt[v]);
if (rt[u]) t[rt[u]].x += w;
}
if (!rt[u]) rt[u] = ++tot;
}
int T, n, m, f[MAXN], w[MAXN], x[4]; ll sum;
int main() {
n = read();
for (int i = 2; i <= n; i++) f[i] = read();
for (int i = 2; i <= n; i++) w[i] = read();
for (T = read(); T--; ) {
x[0] = read(), x[1] = read(), x[2] = read(), x[3] = read(), m = read();
for (int i = 2; i <= n; i++) w[i] ^= x[i & 3];
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1; i <= n; i++) rt[i] = 0; sum = 0;
for (int i = 1; i <= tot; i++) t[i].l = t[i].r = t[i].x = 0; tot = 0;
for (int i = 2; i <= n; i++) g[f[i]].emplace_back(i, w[i]); dfs(1);
for (int u = rt[1]; m--; ) sum += t[u].x, u = merge(t[u].l, t[u].r);
for (int i = 2; i <= n; i++) w[i] ^= x[i & 3];
printf("%lld\n", sum);
}
}
:::
重温一下 dp 式子后,可以发现:我们竟然从始至终都只关心最大的斜率!其他斜率只是朴素地参与排序罢了。于是,我们甚至只需要记录每个位置的斜率最大值,其它斜率扔进一个数组排序即可。这样就做到排序外线性。
:::info[这部分的代码实现]
#include <bits/stdc++.h>
using namespace std;
char buf[1 << 20], *p1 = buf, *p2 = buf;
#define getchar() (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 1 << 20, stdin), p1 == p2) ? EOF : *p1++)
inline
int read() {
int x = 0; char c = getchar();
for (; c < '0' || c > '9'; c = getchar());
for (; c >= '0' && c <= '9'; x = x * 10 + c - '0', c = getchar());
return x;
}
typedef long long ll;
const int MAXN = 2e5 + 10;
vector<pair<int, int>> g[MAXN];
ll dp[MAXN], t[MAXN]; int tp;
void dfs(int u) {
sort(g[u].begin(), g[u].end(),
[](const auto &x, const auto &y) { return x.second > y.second; });
for (auto _ : g[u]) {
int v = _.first, w = _.second; dfs(v);
if (!dp[u]) { dp[u] = dp[v] + w; continue; }
if ((dp[u] -= w) < dp[v]) swap(dp[u], dp[v]);
dp[u] += w, t[tp++] = dp[v];
}
}
int T, n, m, f[MAXN], w[MAXN], x[4]; ll sum;
int main() {
n = read();
for (int i = 2; i <= n; i++) f[i] = read();
for (int i = 2; i <= n; i++) w[i] = read();
for (T = read(); T--; ) {
x[0] = read(), x[1] = read(), x[2] = read(), x[3] = read(), m = read();
for (int i = 2; i <= n; i++) w[i] ^= x[i & 3];
for (int i = 1; i <= n; i++) g[i].clear();
for (int i = 1; i <= n; i++) dp[i] = t[i] = 0; tp = 0;
for (int i = 2; i <= n; i++) g[f[i]].emplace_back(i, w[i]);
for (int i = 2; i <= n; i++) w[i] ^= x[i & 3];
dfs(1), t[tp++] = dp[1], sort(t, t + tp, greater<ll>()), sum = 0;
for (int i = 0; i < m && i < tp; i++) sum += t[i];
printf("%lld\n", sum);
}
}
:::
考虑干掉排序。对于边的排序,我们只需要给每个点的出边进行排序,可以先对所有边排序,再加到树中,因此在外层做计数排序即可。对于斜率实际上只要求全局前 nth_element 即可做到