Search for a command to run...
Progressive hints first, then the full explanation and implementation when you're ready to cash out.
Review status
AI-generated and still unreviewed. Double-check the details before internalizing them.
Hints
Open only as much as you need to keep the solve alive.
First ignore updates. Define as the minimum operations needed to clear the subtree of using paths that start at and go downward. Then ask what child subtrees force independently.
For a fixed node , operations needed inside different child subtrees add, because one endpoint can live in only one child subtree. Separately, itself needs at least operations passing through it.
The fixed-tree recurrence is . The common wrong guess is max over children; nope, branches do not share endpoint operations.
For updates, choose one heavy child and write . Then , with if there is no heavy child.
On a heavy chain, vertex is the function . Functions compose into the same form, so a segment tree can maintain a whole chain; after a point update, bubble changed chain-head values upward through only light edges.
Research checked: I used the official ICPC APAC analysis PDF and an independent traP Static Top Tree writeup. Both point to the same intended core: subtree DP, HLD, and function composition. Public accepted-submission pages were not reliably indexed quickly, so those two were the useful evidence.
Fixed Tree
Let be the minimum number of operations needed to clear the subtree rooted at , if we view an operation as choosing a descendant of and removing one ornament along that downward path.
For a node , there are two lower bounds:
So .
This bound is also achievable. Run optimal plans for all child subtrees. Let their total count be . If , add operations ending exactly at . If , the extra passes through an already-empty are harmless because the operation removes one ornament only if there is one left. That gives
The false assumption to kill here is that child answers should be maximized. They should be summed. Branches do not share endpoint operations; Christmas magic is not a data structure.
Making Updates Fast
Choose a heavy child for every non-leaf vertex using standard HLD by subtree size. Define
where the sum is over light children only. Then
with if has no heavy child.
Now define a function for each vertex:
Along one heavy chain from top to bottom,
Each function has the form . This form is closed under composition. If
then
So the composed function is represented by
Store all vertex functions in HLD order in a segment tree. A heavy chain is contiguous, so a range query gives the chain composition, and evaluating it at gives the value of the chain top.
Processing An Update
When changes:
Each climb crosses one light edge. In HLD, going upward across light edges at least doubles the subtree size, so this happens only times. Each step does one point update and one range query on the segment tree, both .
Complexity
Preprocessing is . Each query is . Memory is .
The implementation avoids recursive DFS. Since , every descendant has a larger index, so subtree sizes and initial DP values can be computed by scanning vertices backwards. That avoids stack overflow on a chain, which is a deeply uncool way to lose a tree problem.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll NEG = -(1LL << 60);
struct Func {
ll a, b;
};
Func identityFunc() {
return {NEG, 0};
}
Func compose(Func f, Func g) {
return {max(f.a, g.a + f.b), f.b + g.b};
}
ll eval(Func f) {
return max(f.a, f.b);
}
struct SegTree {
int n = 1;
vector<Func> st;
SegTree() = default;
explicit SegTree(const vector<Func>& a) {
init(a);
}
void init(const vector<Func>& a) {
n = 1;
while (n < (int)a.size()) n <<= 1;
st.assign(2 * n, identityFunc());
for (int i = 0; i < (int)a.size(); ++i) st[n + i] = a[i];
for (int i = n - 1; i >= 1; --i) st[i] = compose(st[i << 1], st[i << 1 | 1]);
}
void setPoint(int p, Func value) {
p += n;
st[p] = value;
for (p >>= 1; p >= 1; p >>= 1) st[p] = compose(st[p << 1], st[p << 1 | 1]);
}
Func query(int l, int r) const {
Func left = identityFunc(), right = identityFunc();
l += n;
r += n;
while (l < r) {
if (l & 1) left = compose(left, st[l++]);
if (r & 1) right = compose(st[--r], right);
l >>= 1;
r >>= 1;
}
return compose(left, right);
}
};
void solve() {
int n, q;
cin >> n >> q;
vector<int> parent(n, -1);
vector<vector<int>> child(n);
for (int i = 1; i < n; ++i) {
cin >> parent[i];
--parent[i];
child[parent[i]].push_back(i);
}
vector<ll> a(n);
for (ll& x : a) cin >> x;
vector<int> sz(n, 1), heavy(n, -1);
for (int v = n - 1; v >= 1; --v) sz[parent[v]] += sz[v];
for (int v = 0; v < n; ++v) {
for (int c : child[v]) {
if (heavy[v] == -1 || sz[c] > sz[heavy[v]]) heavy[v] = c;
}
}
vector<ll> dp(n, 0), light(n, 0);
for (int v = n - 1; v >= 0; --v) {
for (int c : child[v]) {
if (c != heavy[v]) light[v] += dp[c];
}
ll heavyDp = (heavy[v] == -1 ? 0 : dp[heavy[v]]);
dp[v] = max(a[v], light[v] + heavyDp);
}
vector<int> top(n), tin(n), bottom(n, -1);
int timer = 0;
vector<int> starts = {0};
while (!starts.empty()) {
int start = starts.back();
starts.pop_back();
int v = start, last = start;
while (v != -1) {
top[v] = start;
tin[v] = timer++;
last = v;
for (int c : child[v]) {
if (c != heavy[v]) starts.push_back(c);
}
v = heavy[v];
}
bottom[start] = last;
}
vector<Func> base(n);
for (int v = 0; v < n; ++v) base[tin[v]] = {a[v], light[v]};
SegTree seg(base);
auto chainDp = [&](int start) -> ll {
return eval(seg.query(tin[start], tin[bottom[start]] + 1));
};
cout << dp[0] << '\n';
while (q--) {
int v;
ll x;
cin >> v >> x;
--v;
a[v] = x;
while (v != -1) {
seg.setPoint(tin[v], {a[v], light[v]});
int start = top[v];
ll old = dp[start];
ll now = chainDp(start);
int p = parent[start];
if (p != -1) light[p] += now - old;
dp[start] = now;
v = p;
}
cout << dp[0] << '\n';
}
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
solve();
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
const ll NEG = -(1LL << 60);
struct Func {
ll a, b;
};
Func identityFunc() {
return {NEG, 0};
}
Func compose(Func f, Func g) {
return {max(f.a, g.a + f.b), f.b + g.b};
}
ll eval(Func f) {
return max(f.a, f.b);
}
struct SegTree {
int n = 1;
vector<Func> st;
SegTree() = default;
explicit SegTree(const vector<Func>& a) {
init(a);
}
void init(const vector<Func>& a) {
n = 1;
while (n < (int)a.size()) n <<= 1;
st.assign(2 * n, identityFunc());
for (int i = 0; i < (int)a.size(); ++i) st[n + i] = a[i];
for (int i = n - 1; i >= 1; --i) st[i] = compose(st[i << 1], st[i << 1 | 1]);
}
void setPoint(int p, Func value) {
p += n;
st[p] = value;
for (p >>= 1; p >= 1; p >>= 1) st[p] = compose(st[p << 1], st[p << 1 | 1]);
}
Func query(int l, int r) const {
Func left = identityFunc(), right = identityFunc();
l += n;
r += n;
while (l < r) {
if (l & 1) left = compose(left, st[l++]);
if (r & 1) right = compose(st[--r], right);
l >>= 1;
r >>= 1;
}
return compose(left, right);
}
};
void solve() {
int n, q;
cin >> n >> q;
vector<int> parent(n, -1);
vector<vector<int>> child(n);
for (int i = 1; i < n; ++i) {
cin >> parent[i];
--parent[i];
child[parent[i]].push_back(i);
}
vector<ll> a(n);
for (ll& x : a) cin >> x;
vector<int> sz(n, 1), heavy(n, -1);
for (int v = n - 1; v >= 1; --v) sz[parent[v]] += sz[v];
for (int v = 0; v < n; ++v) {
for (int c : child[v]) {
if (heavy[v] == -1 || sz[c] > sz[heavy[v]]) heavy[v] = c;
}
}
vector<ll> dp(n, 0), light(n, 0);
for (int v = n - 1; v >= 0; --v) {
for (int c : child[v]) {
if (c != heavy[v]) light[v] += dp[c];
}
ll heavyDp = (heavy[v] == -1 ? 0 : dp[heavy[v]]);
dp[v] = max(a[v], light[v] + heavyDp);
}
vector<int> top(n), tin(n), bottom(n, -1);
int timer = 0;
vector<int> starts = {0};
while (!starts.empty()) {
int start = starts.back();
starts.pop_back();
int v = start, last = start;
while (v != -1) {
top[v] = start;
tin[v] = timer++;
last = v;
for (int c : child[v]) {
if (c != heavy[v]) starts.push_back(c);
}
v = heavy[v];
}
bottom[start] = last;
}
vector<Func> base(n);
for (int v = 0; v < n; ++v) base[tin[v]] = {a[v], light[v]};
SegTree seg(base);
auto chainDp = [&](int start) -> ll {
return eval(seg.query(tin[start], tin[bottom[start]] + 1));
};
cout << dp[0] << '\n';
while (q--) {
int v;
ll x;
cin >> v >> x;
--v;
a[v] = x;
while (v != -1) {
seg.setPoint(tin[v], {a[v], light[v]});
int start = top[v];
ll old = dp[start];
ll now = chainDp(start);
int p = parent[start];
if (p != -1) light[p] += now - old;
dp[start] = now;
v = p;
}
cout << dp[0] << '\n';
}
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
solve();
}
}