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.
Each added edge creates exactly one cycle: the new edge plus the unique tree path between its endpoints. In a cactus, every vertex can belong to at most one cycle. So what condition does that impose on the tree paths of the chosen added edges?
After rooting the tree at , think subtree-DP. Let be the best answer using only vertices of the subtree of . Split into two cases: either is not used by any chosen path, or is used by exactly one chosen path. There is no third option — if two chosen paths touch , you're screwed.
Let . If is not used, then clearly . If is used, then the chosen extra edge must have . For a fixed descendant , define the value of the subtree when the whole chain is already blocked. Only side subtrees hanging off that chain can still contribute.
For a blocked chain going through an edge , the contribution you keep from node is all child-subtrees except the one continuing the chain, i.e. . So for a descendant of , the blocked-chain value is For an extra edge with , the total is The fixes the double count at the top.
Now the killer trick. Process nodes bottom-up. Once is known, for every child add to the whole Euler-tour interval of subtree . Then, while processing node , a point query at a descendant gives exactly because ancestors of haven’t been processed yet, so there’s no garbage from above. Therefore for every extra edge with : Group edges by their LCA, use binary lifting for LCA, and a Fenwick tree for range-add / point-query.
The whole problem is really this:
You are given some weighted tree paths. Pick a maximum-weight subset of pairwise vertex-disjoint paths.
That’s it. The cactus wording is just the costume.
Start with a tree. Adding one extra edge creates exactly one cycle: the edge itself plus the unique tree path between and .
If we choose several extra edges, then a tree vertex belongs to a cycle iff it lies on the corresponding tree path.
So the graph is a cactus iff no tree vertex belongs to two different such cycles, i.e. iff the chosen tree paths are pairwise vertex-disjoint.
So we need the maximum total beauty of a subset of given paths that are pairwise vertex-disjoint.
Root the tree at .
Let:
Now ask: what happens at vertex ?
Then no chosen path can go through , so every chosen path lies completely inside one child subtree. The subtrees are independent, therefore the best value is just
Then exactly one chosen extra edge has a tree path containing . Why exactly one? Because if two chosen paths touch , they are not vertex-disjoint. End of story.
Also, that special edge must satisfy
So for each node , we only need to consider extra edges whose LCA is .
That’s the big structural simplification.
Suppose we decide to choose an extra edge whose path uses the chain from down to some descendant . All vertices on that chain are now blocked, so we can only take solutions in the side subtrees hanging off the chain.
Define as the maximum beauty obtainable from the side subtrees when every vertex on the path is blocked.
If the path is
then at each internal node we may keep all child subtrees except the one continuing the blocked chain. So:
Equivalently,
Take an extra edge with . If we choose it, then the two blocked branches are and .
Their side contributions are and . But the side subtrees of are counted in both, so subtract once.
Therefore the value of choosing this edge is
This even works when one endpoint equals , because then .
So the DP recurrence is
So far, so good. The annoying part is computing all these fast.
Look at the formula
Define a weight on every non-root node:
Then
Now here comes the slick part.
When becomes known, we add to every node in the subtree of . Why? Because for any descendant of , the path from an ancestor to includes the edge into .
Using an Euler tour, "add to subtree" becomes a range add on . Then a point query at returns the sum of over all ancestors of whose updates have already been applied.
If we process nodes bottom-up, then while processing a node :
So a point query at a descendant gives exactly the sum of on the path from down to . No contamination from above. Beautiful.
Thus while processing ,
Therefore for an extra edge with ,
That is exactly what we evaluate.
We’ll keep it short and sharp.
For any node , if is not used by any chosen path, the best value inside subtree is .
Reason: then every chosen path lies fully inside one child subtree, and child subtrees are independent.
If is used, then exactly one chosen extra edge has LCA equal to .
Reason: any chosen path using has highest vertex , so its endpoints’ LCA is . Two such paths would both contain , violating vertex-disjointness.
For any descendant of ,
Reason: along the blocked chain, each vertex contributes the optimum of all side child-subtrees not continuing the chain.
While processing node , the Fenwick point query at a descendant equals .
Reason: each node contributes to the whole subtree of . A point receives that value iff is an ancestor of . Since we process bottom-up, updates from ancestors of are not applied yet, so only the path below remains.
For every extra edge with , the best value of solutions using that edge is
Reason: by Lemma 4 this is exactly .
Putting the lemmas together, is the maximum of:
So the recurrence is complete and exact. Hence is the answer.
Total:
Memory:
Which is easily fine for .
No cursed tree knapsack, no quadratic nonsense, no prayer circle required.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Fenwick {
int n;
vector<ll> bit;
Fenwick(int n = 0) { init(n); }
void init(int n_) {
n = n_;
bit.assign(n + 2, 0);
}
void add(int idx, ll val) {
for (; idx <= n; idx += idx & -idx) bit[idx] += val;
}
void range_add(int l, int r, ll val) {
if (l > r) return;
add(l, val);
if (r + 1 <= n) add(r + 1, -val);
}
ll sum(int idx) const {
ll res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
};
struct ExtraEdge {
int u, v, c;
};
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<int> parent(n + 1, 1), depth(n + 1, 0);
vector<vector<int>> children(n + 1);
for (int i = 2; i <= n; ++i) {
cin >> parent[i];
children[parent[i]].push_back(i);
}
constexpr int LOG = 20;
vector<array<int, LOG>> up(n + 1);
up[1].fill(1);
for (int i = 2; i <= n; ++i) {
depth[i] = depth[parent[i]] + 1;
up[i][0] = parent[i];
for (int k = 1; k < LOG; ++k) {
up[i][k] = up[up[i][k - 1]][k - 1];
}
}
auto lca = [&](int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
int diff = depth[a] - depth[b];
for (int k = 0; k < LOG; ++k) {
if (diff & (1 << k)) a = up[a][k];
}
if (a == b) return a;
for (int k = LOG - 1; k >= 0; --k) {
if (up[a][k] != up[b][k]) {
a = up[a][k];
b = up[b][k];
}
}
return up[a][0];
};
// Euler tour for subtree intervals
vector<int> tin(n + 1), tout(n + 1), it(n + 1, 0), st;
st.reserve(n);
st.push_back(1);
int timer = 0;
while (!st.empty()) {
int v = st.back();
if (it[v] == 0) tin[v] = ++timer;
if (it[v] < (int)children[v].size()) {
int u = children[v][it[v]++];
st.push_back(u);
} else {
tout[v] = timer;
st.pop_back();
}
}
vector<vector<ExtraEdge>> byLca(n + 1);
for (int i = 0; i < m; ++i) {
int u, v, c;
cin >> u >> v >> c;
int w = lca(u, v);
byLca[w].push_back({u, v, c});
}
vector<ll> S(n + 1, 0), dp(n + 1, 0);
Fenwick bit(n);
// Because parent[i] < i, processing n..1 is already bottom-up.
for (int v = n; v >= 1; --v) {
ll sumChildren = 0;
for (int u : children[v]) sumChildren += dp[u];
S[v] = sumChildren;
// Activate weights for edges v -> child.
for (int u : children[v]) {
bit.range_add(tin[u], tout[u], S[v] - dp[u]);
}
ll best = S[v];
for (const auto &e : byLca[v]) {
ll cand = (ll)e.c
+ bit.sum(tin[e.u]) + S[e.u]
+ bit.sum(tin[e.v]) + S[e.v]
- S[v];
best = max(best, cand);
}
dp[v] = best;
}
cout << dp[1] << '\n';
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct Fenwick {
int n;
vector<ll> bit;
Fenwick(int n = 0) { init(n); }
void init(int n_) {
n = n_;
bit.assign(n + 2, 0);
}
void add(int idx, ll val) {
for (; idx <= n; idx += idx & -idx) bit[idx] += val;
}
void range_add(int l, int r, ll val) {
if (l > r) return;
add(l, val);
if (r + 1 <= n) add(r + 1, -val);
}
ll sum(int idx) const {
ll res = 0;
for (; idx > 0; idx -= idx & -idx) res += bit[idx];
return res;
}
};
struct ExtraEdge {
int u, v, c;
};
int main() {
setIO();
int n, m;
cin >> n >> m;
vector<int> parent(n + 1, 1), depth(n + 1, 0);
vector<vector<int>> children(n + 1);
for (int i = 2; i <= n; ++i) {
cin >> parent[i];
children[parent[i]].push_back(i);
}
constexpr int LOG = 20;
vector<array<int, LOG>> up(n + 1);
up[1].fill(1);
for (int i = 2; i <= n; ++i) {
depth[i] = depth[parent[i]] + 1;
up[i][0] = parent[i];
for (int k = 1; k < LOG; ++k) {
up[i][k] = up[up[i][k - 1]][k - 1];
}
}
auto lca = [&](int a, int b) {
if (depth[a] < depth[b]) swap(a, b);
int diff = depth[a] - depth[b];
for (int k = 0; k < LOG; ++k) {
if (diff & (1 << k)) a = up[a][k];
}
if (a == b) return a;
for (int k = LOG - 1; k >= 0; --k) {
if (up[a][k] != up[b][k]) {
a = up[a][k];
b = up[b][k];
}
}
return up[a][0];
};
// Euler tour for subtree intervals
vector<int> tin(n + 1), tout(n + 1), it(n + 1, 0), st;
st.reserve(n);
st.push_back(1);
int timer = 0;
while (!st.empty()) {
int v = st.back();
if (it[v] == 0) tin[v] = ++timer;
if (it[v] < (int)children[v].size()) {
int u = children[v][it[v]++];
st.push_back(u);
} else {
tout[v] = timer;
st.pop_back();
}
}
vector<vector<ExtraEdge>> byLca(n + 1);
for (int i = 0; i < m; ++i) {
int u, v, c;
cin >> u >> v >> c;
int w = lca(u, v);
byLca[w].push_back({u, v, c});
}
vector<ll> S(n + 1, 0), dp(n + 1, 0);
Fenwick bit(n);
// Because parent[i] < i, processing n..1 is already bottom-up.
for (int v = n; v >= 1; --v) {
ll sumChildren = 0;
for (int u : children[v]) sumChildren += dp[u];
S[v] = sumChildren;
// Activate weights for edges v -> child.
for (int u : children[v]) {
bit.range_add(tin[u], tout[u], S[v] - dp[u]);
}
ll best = S[v];
for (const auto &e : byLca[v]) {
ll cand = (ll)e.c
+ bit.sum(tin[e.u]) + S[e.u]
+ bit.sum(tin[e.v]) + S[e.v]
- S[v];
best = max(best, cand);
}
dp[v] = best;
}
cout << dp[1] << '\n';
}