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.
Don’t charge an operation per moved nut or per crossed edge. One operation costs once and can move a whole wave of nuts, so the right question is: after how many waves does a vertex become empty?
Ignore initially empty vertices that are just pipes. If removing an empty vertex leaves nuts in at most two neighboring components, it never forces an eating cost after movements. Empty branching vertices are the only zero vertices that can matter.
For an edge , if all future movement operations are chosen on the -side, then the -side disappears after exactly the number of nuts initially on the -side. Until it is empty, exactly one nut crosses that edge per operation.
For each vertex , define as the smallest nut-sum of a component containing after removing one incident edge. Then override for the initially empty non-branching pipe vertices.
Sort all . If you empty exactly vertices using movements, the cheapest required number of operations is the -th smallest , and vertices remain to eat. Try all , plus the case of doing no movement.
The expensive-looking operation is not expensive per nut. It costs once and sends a whole wave of nuts toward the chosen vertex. So forget total moved distance; that idea dies instantly on the samples.
Start with one edge. Remove edge , and let be the component containing . Define
Suppose every future movement operation is chosen somewhere on the -side of the edge. As long as the -side still contains at least one nut, vertex will have a nut by the time it is processed: either it already had one, or some deeper vertex passed one into it. Then sends exactly one nut across edge . So the total number of nuts on the -side drops by exactly per operation until it reaches .
Therefore, the -side disappears after exactly
operations. That is the key invariant. No distance sum. No per-edge payment. Just one nut crossing the boundary per wave.
For a vertex , ask how early it can be made empty by pushing it through one of its incident edges. If we remove edge and choose operations from the -side, the whole component containing disappears after the number of nuts in that component. So the raw disappearance time is
where is the nut-sum of the component containing after removing edge .
There is one annoying zero-vertex detail. If , remove and count how many neighboring components contain nuts. If this count is at most , then is just an empty pipe or outside the useful nut structure. It can be treated as already empty forever, so set
If that count is at least , then several branches can send nuts into while it forwards only one nut, so it may become non-empty. For that vertex, keep the raw value. For positive vertices, also keep the raw value.
Now define all final values this way.
Next, why does sorting these values solve the whole problem? Think about choosing one final surviving vertex and applying all movement operations toward it. Pick as a weighted centroid of the tree using nut counts: every component adjacent to has at most half of the total nuts. Equivalently, maximizes the minimum component-sum value above.
For any other vertex , the neighbor on the path from to lies in the largest nut-containing direction from . Otherwise some branch away from would contain more nuts than the side toward , contradicting the centroid choice. Thus the component of away from has exactly size , and by the edge invariant it disappears after operations toward .
So after movement operations toward this survivor, every vertex with can be made empty, except that at least one vertex must remain non-empty because there is still a positive total number of nuts. Conversely, if , no sequence of only operations can force to be empty: every incident cut containing starts with more than nuts, and at most one nut can leave through the chosen boundary per operation.
There are now only threshold choices.
Doing no movement costs
If we do movements, sort
If exactly vertices are emptied by movement, where , the cheapest required operation count is , and vertices remain to eat. The candidate cost is
Therefore,
Now compute the values.
Root the tree at . Let
For an edge between parent and child :
Use these two values to update the raw minimum for both endpoints.
For the harmless-zero test, we need the neighboring components after deleting a vertex. For the same edge:
Count how many such neighboring component sums are positive. If and that count is at most , force .
Edge cases:
long long; totals and costs go way past int.The DFS work is , and sorting the values costs . Memory usage is .
#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;
ll p, q;
cin >> n >> p >> q;
vector<ll> a(n + 1);
ll total = 0;
int positive = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
total += a[i];
positive += (a[i] > 0);
}
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
if (total == 0 || q == 0) {
cout << 0 << '\n';
continue;
}
vector<int> parent(n + 1, 0), order;
order.reserve(n);
stack<int> st;
st.push(1);
parent[1] = -1;
while (!st.empty()) {
int u = st.top();
st.pop();
order.push_back(u);
for (int v : g[u]) {
if (v == parent[u]) continue;
parent[v] = u;
st.push(v);
}
}
vector<ll> sub(n + 1, 0);
for (int i = n - 1; i >= 0; i--) {
int u = order[i];
sub[u] = a[u];
for (int v : g[u]) {
if (v != parent[u]) sub[u] += sub[v];
}
}
const ll INF = (1LL << 62);
vector<ll> z(n + 1, INF);
vector<int> positiveParts(n + 1, 0);
for (int c = 2; c <= n; c++) {
int par = parent[c];
ll childSide = sub[c];
ll parentSide = total - sub[c];
z[c] = min(z[c], childSide);
z[par] = min(z[par], parentSide);
if (parentSide > 0) positiveParts[c]++;
if (childSide > 0) positiveParts[par]++;
}
vector<ll> vals;
vals.reserve(n);
for (int i = 1; i <= n; i++) {
if (a[i] == 0 && positiveParts[i] <= 2) vals.push_back(0);
else vals.push_back(z[i]);
}
sort(vals.begin(), vals.end());
ll ans = 1LL * positive * q;
for (int emptied = 1; emptied < n; emptied++) {
ans = min(ans, vals[emptied - 1] * p + 1LL * (n - emptied) * q);
}
cout << ans << '\n';
}
}#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;
ll p, q;
cin >> n >> p >> q;
vector<ll> a(n + 1);
ll total = 0;
int positive = 0;
for (int i = 1; i <= n; i++) {
cin >> a[i];
total += a[i];
positive += (a[i] > 0);
}
vector<vector<int>> g(n + 1);
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
g[u].push_back(v);
g[v].push_back(u);
}
if (total == 0 || q == 0) {
cout << 0 << '\n';
continue;
}
vector<int> parent(n + 1, 0), order;
order.reserve(n);
stack<int> st;
st.push(1);
parent[1] = -1;
while (!st.empty()) {
int u = st.top();
st.pop();
order.push_back(u);
for (int v : g[u]) {
if (v == parent[u]) continue;
parent[v] = u;
st.push(v);
}
}
vector<ll> sub(n + 1, 0);
for (int i = n - 1; i >= 0; i--) {
int u = order[i];
sub[u] = a[u];
for (int v : g[u]) {
if (v != parent[u]) sub[u] += sub[v];
}
}
const ll INF = (1LL << 62);
vector<ll> z(n + 1, INF);
vector<int> positiveParts(n + 1, 0);
for (int c = 2; c <= n; c++) {
int par = parent[c];
ll childSide = sub[c];
ll parentSide = total - sub[c];
z[c] = min(z[c], childSide);
z[par] = min(z[par], parentSide);
if (parentSide > 0) positiveParts[c]++;
if (childSide > 0) positiveParts[par]++;
}
vector<ll> vals;
vals.reserve(n);
for (int i = 1; i <= n; i++) {
if (a[i] == 0 && positiveParts[i] <= 2) vals.push_back(0);
else vals.push_back(z[i]);
}
sort(vals.begin(), vals.end());
ll ans = 1LL * positive * q;
for (int emptied = 1; emptied < n; emptied++) {
ans = min(ans, vals[emptied - 1] * p + 1LL * (n - emptied) * q);
}
cout << ans << '\n';
}
}