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.
A coloring is just choosing a nonempty red set . Its cost is , where is the total weight of edges leaving . Also, disconnected is useless to check: one connected component is no more expensive than the whole disconnected mess.
For a connected set , if there is an internal edge and a boundary edge with , delete and keep the side not touching . Its boundary cost does not increase. So was dominated by a smaller connected set.
After repeatedly shrinking dominated sets, the only sets left have every internal edge heavier than every boundary edge. Those are exactly the components created by processing edges from largest to smallest: the maximum-Kruskal reconstruction tree.
For a fixed , let be the boundary cost of component in that reconstruction tree. If is the minimum vertex-weight sum needed inside that component, then
For hard version queries, do not recompute that DP. Each is a convex piecewise-linear integer function. Store slope-change events in a priority queue, merge children small-to-large, then apply the node transition as by finding the crossing point.
Turn colorings into constraints
Pick the red vertices and call the set . The cost is
where and is the total weight of edges with exactly one endpoint in .
So we need minimum such that
for every nonempty red set .
Disconnected sets can be ignored. If has connected components , then because the graph is a tree,
One component is no larger than the whole sum, so if every connected set is safe, every disconnected set is safe too.
Which connected sets matter?
Here is the key domination trick.
Take a connected set . Suppose it has an internal edge and a boundary edge such that
Delete from ; this splits into two connected parts. Keep the side that does not contain the endpoint of inside ; call it .
The boundary of consists of:
Since was an old boundary edge on the other side and , we get
Also , so . Therefore
That means the constraint for is weaker than the constraint for . In plain English: is not worth checking.
So after shrinking away all dominated sets, every remaining proper connected set has this property:
every internal edge is heavier than every boundary edge.
Those sets are exactly the components that appear when we process edges from largest weight to smallest.
Build a maximum-Kruskal reconstruction tree:
Every node of this binary tree represents some original connected component . Let
The root is the whole tree, so .
Why does this cover all important sets? If a connected set has all internal edges heavier than all boundary edges, then all its internal edges are processed before any edge leaving it. At the moment its last internal edge is processed, it becomes exactly one Kruskal component. So it is a node of the reconstruction tree.
Now the original exponential pile of constraints has collapsed to only constraints:
That is the main win. Everything else is implementation pain.
Computing
For a leaf , is just the weighted degree of that vertex.
When two components and are merged by an edge of weight ,
because that edge used to be counted once in each child boundary, and after merging it becomes internal.
The code computes this with edge contributions:
For any reconstruction-tree node, internal edges cancel to , and boundary edges contribute exactly once. Clean and nasty, as intended.
Fixed DP
Let be the minimum total vertex weight needed inside component so that all constraints inside the reconstruction subtree of are satisfied.
If has children, their vertex sets are disjoint, so before considering itself we need
Then node requires
If the children already force enough total weight, do nothing. Otherwise add the missing amount anywhere inside ; extra weight only helps descendants.
Therefore
For leaves, the child sum is , so this is .
The answer is .
Hard version: many queries
Doing that DP for every query is , which is dead on arrival.
Instead, treat as a function of . Every function here is convex and piecewise-linear over integer :
We store each function by slope-change events. An event means: starting at integer point , increase the slope by .
To evaluate a queue of events in increasing point order, maintain:
A: current slope,B: value at previous event point,pv: previous event point.When moving to event point p, do
then update the slope.
A leaf is just one event:
For an internal node:
Merging is small-to-large with priority queues.
Applying the max with a line
Let the current merged function be and the new line be
If is not before the first breakpoint of , the line never beats for relevant values. Otherwise, starts above , and because is convex and eventually has larger slope, catches up once.
We pop events while sweeping . On the current segment,
Solve the crossing with :
so
If the crossing lies before the next event, we know exactly where the function switches from the line back to .
Since queries are integers, if
then:
mid, the line is used,mid+1, is used.The three inserted events in the code encode exactly that integer switch. It looks cursed, but it is just floor/ceil bookkeeping. This is the part where off-by-one errors show up with a chair and a baseball bat.
Finally, after building the root function, sort queries by , sweep the root event queue once, and evaluate answers.
Complexity
For each test case:
Memory is .
All answer values fit in long long.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
int r = x;
while (p[r] != r) r = p[r];
while (p[x] != x) {
int y = p[x];
p[x] = r;
x = y;
}
return r;
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<array<ll, 3>> edges;
edges.reserve(n - 1);
for (int i = 0; i < n - 1; i++) {
ll u, v, w;
cin >> u >> v >> w;
--u;
--v;
edges.push_back({w, u, v});
}
sort(edges.rbegin(), edges.rend());
int m = 2 * n - 1;
DSU dsu(m);
vector<ll> cost(m, 0);
vector<array<int, 2>> child(m);
for (int i = 0; i < n - 1; i++) {
ll w = edges[i][0];
int a = (int)edges[i][1];
int b = (int)edges[i][2];
int u = dsu.find(a);
int v = dsu.find(b);
int id = n + i;
dsu.p[u] = id;
dsu.p[v] = id;
child[id] = {u, v};
cost[a] += w;
cost[b] += w;
cost[id] -= 2 * w;
}
for (int i = n; i < m; i++) {
cost[i] += cost[child[i][0]] + cost[child[i][1]];
}
using Event = array<ll, 2>; // {point, slope increase}
vector<priority_queue<Event, vector<Event>, greater<Event>>> pq(n);
vector<int> bucket(m);
iota(bucket.begin(), bucket.end(), 0);
for (int i = 0; i < m; i++) {
if (i < n) {
pq[i].push({cost[i], 1});
continue;
}
int left = child[i][0];
int right = child[i][1];
if (pq[bucket[left]].size() < pq[bucket[right]].size()) {
swap(child[i][0], child[i][1]);
left = child[i][0];
right = child[i][1];
}
bucket[i] = bucket[left];
int big = bucket[left];
int small = bucket[right];
while (!pq[small].empty()) {
pq[big].push(pq[small].top());
pq[small].pop();
}
if (cost[i] >= pq[big].top()[0]) continue;
if (cost[i] >= 2000000000LL) continue;
ll A = 0, B = 0, pv = -1;
while (!pq[big].empty()) {
auto [point, slope] = pq[big].top();
pq[big].pop();
B += A * (point - pv);
pv = point;
A += slope;
__int128 U = (__int128)A * pv - B - cost[i];
ll L = A - 1;
if (L > 0) {
if (pq[big].empty() || U < (__int128)pq[big].top()[0] * L) {
ll mid = (ll)(U / L);
ll f1 = mid - cost[i];
ll f2 = (ll)((__int128)(mid + 1 - pv) * A + B);
pq[big].push({cost[i], 1});
pq[big].push({mid, f2 - f1 - 1});
pq[big].push({mid + 1, A - (f2 - f1)});
break;
}
} else if (pq[big].empty()) {
pq[big].push({cost[i], 1});
break;
}
}
}
int q;
cin >> q;
vector<array<ll, 2>> queries(q);
for (int i = 0; i < q; i++) {
cin >> queries[i][0];
queries[i][1] = i;
}
sort(queries.begin(), queries.end());
vector<ll> ans(q);
int rootBucket = bucket[m - 1];
ll A = 0, B = 0, pv = -1;
for (auto [x, id] : queries) {
while (!pq[rootBucket].empty() && pq[rootBucket].top()[0] <= x) {
auto [point, slope] = pq[rootBucket].top();
pq[rootBucket].pop();
B += A * (point - pv);
pv = point;
A += slope;
}
ans[(int)id] = (ll)((__int128)(x - pv) * A + B);
}
for (ll v : ans) {
cout << v << '\n';
}
}
return 0;
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct DSU {
vector<int> p;
DSU(int n = 0) { init(n); }
void init(int n) {
p.resize(n);
iota(p.begin(), p.end(), 0);
}
int find(int x) {
int r = x;
while (p[r] != r) r = p[r];
while (p[x] != x) {
int y = p[x];
p[x] = r;
x = y;
}
return r;
}
};
int main() {
setIO();
int T;
cin >> T;
while (T--) {
int n;
cin >> n;
vector<array<ll, 3>> edges;
edges.reserve(n - 1);
for (int i = 0; i < n - 1; i++) {
ll u, v, w;
cin >> u >> v >> w;
--u;
--v;
edges.push_back({w, u, v});
}
sort(edges.rbegin(), edges.rend());
int m = 2 * n - 1;
DSU dsu(m);
vector<ll> cost(m, 0);
vector<array<int, 2>> child(m);
for (int i = 0; i < n - 1; i++) {
ll w = edges[i][0];
int a = (int)edges[i][1];
int b = (int)edges[i][2];
int u = dsu.find(a);
int v = dsu.find(b);
int id = n + i;
dsu.p[u] = id;
dsu.p[v] = id;
child[id] = {u, v};
cost[a] += w;
cost[b] += w;
cost[id] -= 2 * w;
}
for (int i = n; i < m; i++) {
cost[i] += cost[child[i][0]] + cost[child[i][1]];
}
using Event = array<ll, 2>; // {point, slope increase}
vector<priority_queue<Event, vector<Event>, greater<Event>>> pq(n);
vector<int> bucket(m);
iota(bucket.begin(), bucket.end(), 0);
for (int i = 0; i < m; i++) {
if (i < n) {
pq[i].push({cost[i], 1});
continue;
}
int left = child[i][0];
int right = child[i][1];
if (pq[bucket[left]].size() < pq[bucket[right]].size()) {
swap(child[i][0], child[i][1]);
left = child[i][0];
right = child[i][1];
}
bucket[i] = bucket[left];
int big = bucket[left];
int small = bucket[right];
while (!pq[small].empty()) {
pq[big].push(pq[small].top());
pq[small].pop();
}
if (cost[i] >= pq[big].top()[0]) continue;
if (cost[i] >= 2000000000LL) continue;
ll A = 0, B = 0, pv = -1;
while (!pq[big].empty()) {
auto [point, slope] = pq[big].top();
pq[big].pop();
B += A * (point - pv);
pv = point;
A += slope;
__int128 U = (__int128)A * pv - B - cost[i];
ll L = A - 1;
if (L > 0) {
if (pq[big].empty() || U < (__int128)pq[big].top()[0] * L) {
ll mid = (ll)(U / L);
ll f1 = mid - cost[i];
ll f2 = (ll)((__int128)(mid + 1 - pv) * A + B);
pq[big].push({cost[i], 1});
pq[big].push({mid, f2 - f1 - 1});
pq[big].push({mid + 1, A - (f2 - f1)});
break;
}
} else if (pq[big].empty()) {
pq[big].push({cost[i], 1});
break;
}
}
}
int q;
cin >> q;
vector<array<ll, 2>> queries(q);
for (int i = 0; i < q; i++) {
cin >> queries[i][0];
queries[i][1] = i;
}
sort(queries.begin(), queries.end());
vector<ll> ans(q);
int rootBucket = bucket[m - 1];
ll A = 0, B = 0, pv = -1;
for (auto [x, id] : queries) {
while (!pq[rootBucket].empty() && pq[rootBucket].top()[0] <= x) {
auto [point, slope] = pq[rootBucket].top();
pq[rootBucket].pop();
B += A * (point - pv);
pv = point;
A += slope;
}
ans[(int)id] = (ll)((__int128)(x - pv) * A + B);
}
for (ll v : ans) {
cout << v << '\n';
}
}
return 0;
}