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 DP over all states. The last move only bans one subtraction, so for a fixed health, the losing forbidden moves can only be: none, exactly one, or all of them.
Use states , where is the forbidden damage and means nothing is forbidden. Call evil if is losing. Evil health values are exactly the NO answers.
For even , the evil values are just multiples of . Bob answers with ; since is odd, this never repeats .
For odd , the mirror strategy breaks only at the middle value . Consecutive evil values are still very tight: after an evil value , the next evil value is either or .
So for each fixed odd , generate evil values only. To decide whether is evil, write a recursive check for the state where moving directly back to the previous evil value is unavailable; only three candidate move types can matter.
Let the real state be :
A move chooses , , and either kills immediately if , or sends the game to .
Call a state losing if the player to move loses. Define:
The answer is NO exactly for evil .
Why each health has only three possible types
For a fixed health , look at the set of winning damages if they are legal. A damage is in if it kills immediately, or if it moves to a losing state .
Now consider . This state is losing exactly when all winning damages are banned. But only one damage is banned.
So:
That is the big compression. We never need a full table of size .
Evil values are sparse
Suppose are both evil and . From , the current player can subtract and move to health . Since is evil, is losing too, so this would be a winning move. Contradiction.
Therefore consecutive evil values differ by at least .
Even
If is even, then is odd. The evil values are exactly multiples of .
From a multiple of , if the first player subtracts , Bob subtracts
This is always in , and because is odd, it is never equal to . So Bob's reply is legal. Every pair of moves removes exactly health, and Bob gets the final hit.
If is not a multiple of , the first player moves down to the previous multiple and wins.
So for even :
Odd
Now is even, and the mirror reply breaks at the middle move:
If the opponent plays , replying with is illegal. That one annoying move is the whole reason this is not a baby modulo problem.
Let the evil values for this fixed odd be
We use as a fake boundary value. The first real evil value is always .
From an evil value , every health
is not evil, because the current player can move directly back to unless exactly that move is forbidden.
So the next evil value is at least .
A structural argument shows it is never later than . In short: if is not evil, the only way that can happen is a special neutral state exactly halfway back through the broken middle move. That special event cannot also save , so one of these two positions is evil.
Thus:
So we only need to decide whether is evil.
The recursive check
Define win(n, j) as follows:
The structural lemmas reduce all possible useful moves to at most three cases.
Depending on whether the previous evil gap was or , there is one direct tactical move that reaches and guarantees control of the game. This is the constant-time condition in the code.
If is even, play
Then the opponent is at health , and their direct move to is also , which is now forbidden. So the opponent's state is exactly another win check. If the opponent cannot win there, this move wins.
Similarly, if is even and half of it is at most , play
This blocks the opponent's direct move to the previous evil value, so recurse with index .
If none of these works, the state is losing under the blocked-direct-move condition.
Now generation is simple:
now.now + m + 1 with win.now + m + 2.now + m + 1.Because evil values are spaced by about , for one fixed we generate only values. Across all queried odd , this totals about
for , which is fine.
Algorithm
For each test case:
n % (m+1).NO iff its appears in the evil list.The constraints pretend this wants a giant DP. It does not. The game is mostly sparse bad positions plus one cursed middle move.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct OddGame {
int m;
vector<int> evil;
// Is (n, n - evil[j]) winning?
bool win(int n, int j) {
if (n < m + 1) return true;
if (n == m + 1) return false;
int gap = evil[j] - evil[j - 1];
int d = n - evil[j];
if (((gap == m + 2 && d + 1 == (m + 1) / 2) ||
(gap == m + 1 && d + 1 == m)) &&
n < evil[j] + m) {
return true;
}
if (d % 2 == 0) {
if (!win(n - d / 2, j)) return true;
}
int dprev = n - evil[j - 1];
if (dprev % 2 == 0 && dprev / 2 <= m) {
if (!win(n - dprev / 2, j - 1)) return true;
}
return false;
}
vector<int> build(int M, int maxn) {
m = M;
evil.clear();
evil.push_back(0);
evil.push_back(m + 1);
int now = m + 1;
while (now + m + 1 <= maxn) {
if (win(now + m + 1, (int)evil.size() - 1)) now += m + 2;
else now += m + 1;
evil.push_back(now);
}
return evil;
}
};
int main() {
setIO();
const int MAXV = 1'000'000;
int t;
cin >> t;
vector<pair<int, int>> queries(t);
vector<int> need(MAXV + 1, 0);
for (int i = 0; i < t; i++) {
int n, m;
cin >> n >> m;
queries[i] = {n, m};
if (m & 1) need[m] = max(need[m], n);
}
vector<vector<int>> bad(MAXV + 1);
OddGame game;
for (int m = 3; m <= MAXV; m += 2) {
if (need[m]) bad[m] = game.build(m, need[m]);
}
for (auto [n, m] : queries) {
bool lose;
if (m % 2 == 0) {
lose = (n % (m + 1) == 0);
} else {
lose = binary_search(bad[m].begin(), bad[m].end(), n);
}
cout << (lose ? "NO" : "YES") << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
struct OddGame {
int m;
vector<int> evil;
// Is (n, n - evil[j]) winning?
bool win(int n, int j) {
if (n < m + 1) return true;
if (n == m + 1) return false;
int gap = evil[j] - evil[j - 1];
int d = n - evil[j];
if (((gap == m + 2 && d + 1 == (m + 1) / 2) ||
(gap == m + 1 && d + 1 == m)) &&
n < evil[j] + m) {
return true;
}
if (d % 2 == 0) {
if (!win(n - d / 2, j)) return true;
}
int dprev = n - evil[j - 1];
if (dprev % 2 == 0 && dprev / 2 <= m) {
if (!win(n - dprev / 2, j - 1)) return true;
}
return false;
}
vector<int> build(int M, int maxn) {
m = M;
evil.clear();
evil.push_back(0);
evil.push_back(m + 1);
int now = m + 1;
while (now + m + 1 <= maxn) {
if (win(now + m + 1, (int)evil.size() - 1)) now += m + 2;
else now += m + 1;
evil.push_back(now);
}
return evil;
}
};
int main() {
setIO();
const int MAXV = 1'000'000;
int t;
cin >> t;
vector<pair<int, int>> queries(t);
vector<int> need(MAXV + 1, 0);
for (int i = 0; i < t; i++) {
int n, m;
cin >> n >> m;
queries[i] = {n, m};
if (m & 1) need[m] = max(need[m], n);
}
vector<vector<int>> bad(MAXV + 1);
OddGame game;
for (int m = 3; m <= MAXV; m += 2) {
if (need[m]) bad[m] = game.build(m, need[m]);
}
for (auto [n, m] : queries) {
bool lose;
if (m % 2 == 0) {
lose = (n % (m + 1) == 0);
} else {
lose = binary_search(bad[m].begin(), bad[m].end(), n);
}
cout << (lose ? "NO" : "YES") << '\n';
}
}