Author : tnaveen2308
Required Knowledge : Graphs, Posets, DP, Advanced Data Structures
Time Complexity :
Editorialist : tnaveen2308
First, how do we find the answer in any time complexity? Let us construct a partially ordered set where each element is a single spy in one of the assignments. For two elements and
we put
if
and
could possibly be two assignments that the same spy can handle one after the other, that is, there is enough time to get from one safe-house to another, so that
, where
is the distance between the safe-houses, and
and
are respective day numbers of the two assignments. We can now see that the answer is the smallest number of
needed to cover all the vertices (a chain is a set of pairwise comparable elements of a p.o.set). By Dilworth's theorem, this is equal to the size of the largest
(a set of pairwise incomparable elements). Clearly, if a largest antichain contains a spy from an assignment, then it must include all spies from this assignment as well. We can now solve the problem in something like
time by trying all subsets of assignments.
To get a better complexity we need to use the tree structure. To start, how do we check if a set of assignments forms an antichain? Here's one criterion that works:
Proposition. A set of assignments is an antichain iff:
, a subset of
lying in that subtree is an antichain.
such that it is impossible to reach
at time
from any assignment in
(possibly travelling back in time).Indeed, if both of these conditions hold, than for any two assignments and
happening in different subtrees we have
, hence these assignments are incomparable. On the other hand, if
is an antichain, for any assignment
there is an interval of time moments
such that we cannot reach the root at any of times in
. If two assignments
and
are incomparable, then
. But since we have a set of intervals with pairwise non-empty intersections, they must all share a common point, hence our time moment
exists.
Note that in some cases is necessarily a non-integer: if there are two safe-houses at distance 1 from the root, and there are assignments happening at days 1 and 2 respectively at these safe-houses, than any
will suffice. However,
can always be either an integer or a half-integer, hence we can multiply all times and distances by 2 and assume
is integer.
The solution is going to be subtree dynamic programming — the total weight of the largest antichain in the subtree of
such that no assignment can reach
at time
. Recalculation is a bit tricky, so let's go step by step. First, if we have values of
, and no assignments take place at
, how do we change
as we travel up the edge to the top of
? If the length of this edge is
, then the new values
satisfy
. Let's call this procedure "advancing
steps forward".
How do we account for assignments at ? For an assignment
with
spies we cannot simply add
to
since we can obviously reach
at time
from this assignment. Instead, the correct procedure is:
for all assignments;
steps.Finally, to combine the answers from the subtrees of a safe-house
(after the advancements were made in them), we simply add them element-wise for each
:
.
Of course, we cannot store the DP values themselves since there are too much of them to store. Instead, we will need to use a data structure that will maintain a piecewise constant function. More specifically, let us maintain the positions where the function changes values: for each such that
we store the pair
. How all of the above manipulations look like in terms of this difference structure?
value, but for most implementations this is not necessary)
steps forward, the borders with positive differences will more
units to the left, and negative differences will move to the right. When two borders collide, then some of them get eliminated. Consider the function 10, 1, 5, with two differences
and
. After advancing this one step, the new function is 10, 10, 5, so there is now a single difference
. If we keep track of when and where these collisions take place, we can make changes to the structure accordingly.The structure of choice here is a treap of differences. When we add two functions, we insert all elements of the smaller treap into the larger one; a standard argument shows that at most insertions will take place overall. To keep track of collisions, we'll have to maintain collision times for all adjacent border pairs, and store the minimal value in the root of each subtree. This may seem cumbersome, but every adjacent pair of elements occurs either as (the rightmost safe-house in the left subtree of
,
), or (
, the leftmost safe-house in the right subtree of
), hence the standard
function in the treap implementation can handle this.
One further detail is that we don't want to change the time values in our treaps. Instead, we'll use "lazy" time shifting, that is, we'll keep an additional value associated with each treap, and assume that for entries
with
the actual time should be
, and with
the time should be
. This way, we won't need to actually change anything in the treap when advancing (apart from resolving collisions).
#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, m, ans, s;
vector<pair<int, int>> graph[N], q[N];
struct node {
int t;
map<int, int> f, g;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
void update(int x, int v) {
if (v == 1) {
auto it = g.upper_bound(x - 2 * t);
if (it != g.begin())
pq.emplace(x - prev(it)->first, x);
} else {
auto it = f.lower_bound(x + 2 * t);
if (it != f.end())
pq.emplace(it->first - x, it->first);
}
}
void cal(int d) {
while (pq.size() && pq.top().first <= 2 * (t + d)) {
int x = pq.top().second, y = pq.top().second - pq.top().first;
pq.pop();
auto ix = f.find(x), iy = g.find(y);
if (ix == f.end() || iy == g.end())
continue;
if (ix->second + iy->second < 0) {
iy->second += ix->second;
f.erase(ix);
update(iy->first, -1);
} else {
ix->second += iy->second;
g.erase(iy);
update(ix->first, 1);
}
}
t += d;
}
void add(int p, int x) {
if (x > 0) {
f[p + t] += x;
update(p + t, 1);
} else {
g[p - t] += x;
update(p - t, -1);
}
}
int ask(int p) {
int s = 0;
if (f.find(p + t) != f.end())
s += f[p + t];
if (g.find(p - t) != f.end())
s += g[p - t];
return s;
}
int sz() {
return f.size() + g.size();
}
};
node dp[N];
map<int, int> mp;
void merge(node &a, node &b) {
if (a.sz() < b.sz())
swap(a, b);
for (auto [d, x]: b.f)
a.add(d - b.t, x);
for (auto [d, x]: b.g)
a.add(d + b.t, x);
}
void dfs(int u, int f) {
for (auto [v, w]: graph[u]) {
if (v == f)
continue;
dfs(v, u);
for (auto &[d, x]: q[v])
x -= max(0, max(-dp[v].ask(d), dp[v].ask(d + 1)));
dp[v].cal(1);
for (auto [d, x]: q[v])
if (x > 0)
dp[v].add(d, x), dp[v].add(d + 1, -x);
dp[v].cal(w - 1);
merge(dp[u], dp[v]);
}
}
int main() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v, w;
cin >> u >> v >> w;
graph[u].emplace_back(v, w * 2);
graph[v].emplace_back(u, w * 2);
}
n++;
graph[n].emplace_back(1, 2);
graph[1].emplace_back(n, 2);
cin >> m ;
for (int i = 1; i <= m; i++) {
int d, x, u;
cin >> d >> x >> u;
q[u].emplace_back(d * 2, x);
}
dfs(n, 0);
for (auto [d, x]: dp[n].f){
mp[d - dp[n].t] += x;
}
for (auto [d, x]: dp[n].g){
mp[d + dp[n].t] += x;
}
for (auto i: mp) {
s += i.second;
ans = max(ans, s);
}
cout << ans << '\n';
return 0;
}