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.
Try to process the tree bottom-up. For a node , ask yourself: after I fix all children optimally, what is the only information about each child that the parent actually cares about?
You do not need the exact final values inside a child subtree. What matters is the maximum amount that subtree can still "pass upward" to its parent without using extra operations.
Let be the sum of what all children can pass to . If , then the children simply cannot make large enough. At that point, one more operation is unavoidable — and doing it at is at least as good as doing it deeper.
If , then no new operation is needed at . The best this subtree can offer upward is not , but , because itself cannot exceed .
Define = maximum amount subtree can pass to its parent using the minimum number of operations in that subtree. Then the transition is
r_v, & \text{if } \sum mx_u < l_v,\\ \min\left(r_v, \sum mx_u\right), & \text{otherwise.} \end{cases}$$ Each time the first case happens, increment the answer. Since $p_i < i$, you can process vertices in reverse order $n,n-1,\dots,1$ — no recursion needed.Forget the scary operation definition for a second. The whole problem collapses into a very small DP once you look at it the right way.
For each node , we only care about two things about its subtree:
Let's define:
That second definition is the entire damn trick.
Suppose child has already been solved, and its subtree can contribute any value in to .
Then if has children , together they can contribute any total value in
Why? Because each child independently gives an interval , and the sum of intervals is still an interval. No cursed subset-DP nonsense here.
So after processing all children, node sees just one number:
Now what?
Then even if every child subtree helps as much as possible, node still cannot reach its lower bound.
So one more operation is unavoidable.
And if we need one extra operation anyway, the best place to start it is at itself. Starting it deeper is never better: one operation at can directly make as large as we want up to , which is as good as it gets for helping ancestors too.
So in this case:
Why ? Because with one operation at , we can keep the subtree valid and make the contribution to the parent be any value from to .
Then we do not need a new operation at .
The children can already make large enough.
The only thing that limits how useful this subtree is to the parent is the upper bound of itself. So the maximum upward contribution is
And the operation count stays
Why is that enough? Because the children can make receive any amount up to , and once receives some value , the operations can be tuned so that the parent gets any value . So every value in is achievable upward.
So the entire DP is:
And every time , we add to the answer.
That's it. Seriously. The operation definition looks like it wants a PhD thesis; the solution is just a greedy postorder DP.
The input guarantees . So every parent has a smaller index than its children.
That means processing nodes in order
is already a valid postorder traversal.
So you don't even need DFS recursion. Just accumulate each node's into its parent.
For each test case, every node is processed once.
Across all test cases, that's , which fits easily.
If is a leaf, then . Since , we always have , so we must spend one operation there, and . That matches the recurrence perfectly.
So yeah — bottom-up, sum child capacities, and whenever the sum is too small, smack one operation on the node. Clean, greedy, accepted.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> p(n + 1, 0);
for (int i = 2; i <= n; ++i) cin >> p[i];
vector<ll> l(n + 1), r(n + 1);
for (int i = 1; i <= n; ++i) cin >> l[i] >> r[i];
vector<ll> sum(n + 1, 0), mx(n + 1, 0);
int ans = 0;
// Because p[i] < i, iterating from n down to 1 is postorder.
for (int v = n; v >= 1; --v) {
if (sum[v] < l[v]) {
++ans;
mx[v] = r[v];
} else {
mx[v] = min(sum[v], r[v]);
}
if (v != 1) sum[p[v]] += mx[v];
}
cout << ans << '\n';
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
int main() {
setIO();
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> p(n + 1, 0);
for (int i = 2; i <= n; ++i) cin >> p[i];
vector<ll> l(n + 1), r(n + 1);
for (int i = 1; i <= n; ++i) cin >> l[i] >> r[i];
vector<ll> sum(n + 1, 0), mx(n + 1, 0);
int ans = 0;
// Because p[i] < i, iterating from n down to 1 is postorder.
for (int v = n; v >= 1; --v) {
if (sum[v] < l[v]) {
++ans;
mx[v] = r[v];
} else {
mx[v] = min(sum[v], r[v]);
}
if (v != 1) sum[p[v]] += mx[v];
}
cout << ans << '\n';
}
return 0;
}