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.
Do not try to recompute the whole game after every red update. The graph is a DAG and colors only change blue red, so the answer structure only gets worse for Cry over time.
The turn matters. Define two states for every vertex: = Cry wins starting at when Cry moves next, and = Cry wins starting at when River moves next. For a blue non-sink node, is an OR over children, while is an AND over children.
Because the graph is acyclic, only descendants of a newly-red vertex can affect a node. Equivalently, when something changes, push that information backward through reverse edges.
Monotonicity is the whole trick: once a state becomes losing for Cry, it never becomes winning again. So maintain only the states that have already become losing, and propagate newly-losing states once.
A blue node's becomes losing as soon as one child has losing . A blue node's becomes losing only after all children have losing . Maintain a counter of children with losing for each node, plus queues for newly-losing and states.
We need answer a changing game on a DAG. The nasty part is that the set of red vertices grows over time, so a plain DP per query would be very dead: is not passing anything except maybe a vibe check.
The good news: painting nodes red only makes Cry's life worse. That monotonicity lets us process every state at most once.
Game DP with turns
There are really two states per vertex:
Terminal rules:
For a blue non-sink vertex:
Cry chooses the move, so he only needs one child that is good for him.
River chooses the move, so Cry wins only if every possible River move still wins for Cry.
Queries of type ask for under the current red set.
What changes when a node becomes red?
When vertex is painted red, both and become losing for Cry. Then this can affect its parents:
That second condition is where a counter comes in.
Let:
Then becomes losing exactly when:
with one important detail: do not mark sinks losing from this rule. A blue sink is an immediate win for Cry. It only loses if it itself is painted red.
Why this is fast
Each of the states, and , can switch from winning to losing at most once. Every time a state switches, we scan reverse edges of that vertex. So the total work over all updates is , plus per query.
No topological sorting is even needed. The DAG guarantees the game makes sense, but the propagation itself only relies on monotone state changes.
Propagation rules
Maintain:
loseC[u]: whether is losing for Cry.loseR[u]: whether is losing for Cry.badRcnt[u]: number of outgoing children with loseR[child] = true.When a vertex becomes red, call markC(u) and markR(u).
When markC(v) happens:
markR(p).Reason: River at can choose , and then Cry loses.
When markR(v) happens:
badRcnt[p].badRcnt[p] == outdegree[p], call markC(p).Reason: Cry at has no safe move left.
Because markC and markR ignore states already marked losing, every edge contributes only a constant number of times. Clean, brutal, done.
#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, m, q;
cin >> n >> m >> q;
vector<vector<int>> rev(n + 1);
vector<int> out(n + 1, 0);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
out[u]++;
rev[v].push_back(u);
}
vector<char> red(n + 1, 0), loseC(n + 1, 0), loseR(n + 1, 0);
vector<int> badRcnt(n + 1, 0);
queue<pair<int, int>> que; // type 0 = C state, type 1 = R state
auto addC = [&](int u) {
if (!loseC[u]) {
loseC[u] = 1;
que.push({0, u});
}
};
auto addR = [&](int u) {
if (!loseR[u]) {
loseR[u] = 1;
que.push({1, u});
}
};
auto propagate = [&]() {
while (!que.empty()) {
auto [type, v] = que.front();
que.pop();
if (type == 0) {
for (int p : rev[v]) {
if (!red[p] && !loseR[p]) {
loseR[p] = 1;
que.push({1, p});
}
}
} else {
for (int p : rev[v]) {
badRcnt[p]++;
if (!red[p] && !loseC[p] && badRcnt[p] == out[p]) {
loseC[p] = 1;
que.push({0, p});
}
}
}
}
};
while (q--) {
int x, u;
cin >> x >> u;
if (x == 1) {
red[u] = 1;
addC(u);
addR(u);
propagate();
} else {
cout << (loseC[u] ? "NO\n" : "YES\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, m, q;
cin >> n >> m >> q;
vector<vector<int>> rev(n + 1);
vector<int> out(n + 1, 0);
for (int i = 0; i < m; i++) {
int u, v;
cin >> u >> v;
out[u]++;
rev[v].push_back(u);
}
vector<char> red(n + 1, 0), loseC(n + 1, 0), loseR(n + 1, 0);
vector<int> badRcnt(n + 1, 0);
queue<pair<int, int>> que; // type 0 = C state, type 1 = R state
auto addC = [&](int u) {
if (!loseC[u]) {
loseC[u] = 1;
que.push({0, u});
}
};
auto addR = [&](int u) {
if (!loseR[u]) {
loseR[u] = 1;
que.push({1, u});
}
};
auto propagate = [&]() {
while (!que.empty()) {
auto [type, v] = que.front();
que.pop();
if (type == 0) {
for (int p : rev[v]) {
if (!red[p] && !loseR[p]) {
loseR[p] = 1;
que.push({1, p});
}
}
} else {
for (int p : rev[v]) {
badRcnt[p]++;
if (!red[p] && !loseC[p] && badRcnt[p] == out[p]) {
loseC[p] = 1;
que.push({0, p});
}
}
}
}
};
while (q--) {
int x, u;
cin >> x >> u;
if (x == 1) {
red[u] = 1;
addC(u);
addR(u);
propagate();
} else {
cout << (loseC[u] ? "NO\n" : "YES\n");
}
}
}
return 0;
}