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.
Because , the actual stone count is basically one bit of information: is this pile a or not? Every final pile gives at least , and you only need to count when the final pile is .
Do not treat good indices as attached to original piles. After every deletion, piles are re-indexed, and index is legal if the current index number is in the good-index set.
Represent a current position with piles by a bitmask of length , where bit is iff the current -th pile has value . The game state also needs whose turn it is.
For a fixed mask, Alice wants at least one deletion that eventually leaves a , so her transition is OR. Bob wants to avoid leaving a , so for the outcome to still be after Bob moves, all legal deletions must lead to . That is AND.
Build two tables by length: for Alice to move and for Bob to move. Base case: with one pile, the answer is just that bit. Then delete each legal current index and shift the mask down.
Core idea
The easy version is only scary until you notice . Then the game is not about arbitrary pile sizes. Each pile is either:
So for every configuration, the final value is:
For , there are configurations, so the total answer is:
For , there is only one configuration and the final value is always , so the answer is just .
That is the whole reduction. Now we only need to count binary configurations where Alice can force the final survivor to be a .
Important detail: good indices are current indices
This is the easiest place to screw up the problem.
The good set is a set of index numbers. After removing a pile, the remaining piles are re-indexed. So if index is good, that means “the current second pile may be removed,” not “the original pile that started at position is special.”
Because of that, the game state only depends on the current sequence of pile values, not on original pile identities.
Binary mask state
Encode the current sequence of piles as a bitmask of length :
Define two DP tables:
if, from this current sequence of length with Alice to move, the final pile will be under optimal play.
if, from this current sequence of length with Bob to move, the final pile will be under optimal play.
These are Boolean values, not counts.
Base case
If , the game is already over. No one moves. The outcome is just the only remaining pile:
Here is either or .
Transition
Suppose there are piles.
A legal move chooses some current index such that is marked good and , then removes that pile. In mask terms, removing bit gives a new mask of length .
Let mean deleting the -th current bit and shifting all later bits left.
Alice chooses the best move for herself. She only needs one move that eventually leaves a :
Bob chooses the worst move for Alice. The result is still only if every legal move Bob can make still leaves Alice with a forced :
That OR/AND flip is the entire minimax game. No probability, no “expected value,” no funny business.
Counting the answer
After building up to length , the initial position has Alice to move, so we count:
For :
The term is the baseline contributed by every configuration. The term adds the extra for configurations where the final value is .
Why this is enough
The original values are only or , so knowing which current piles are fully determines whether Alice prefers a move and whether Bob hates it. Also, legal moves depend only on current indices, and current indices are already represented by the order of bits in the mask. So the mask plus whose turn it is is a complete state.
The induction is straightforward:
So the DP computes exactly the optimal-play outcome for every binary configuration.
Complexity
For each length , there are masks, and we try up to good indices.
Total complexity per test case:
up to a small constant. With and the global bound, this is completely fine.
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
static int removeBit(int mask, int pos) {
int lower = mask & ((1 << pos) - 1);
int upper = mask >> (pos + 1);
return lower | (upper << pos);
}
int main() {
setIO();
const int MOD = 1000000007;
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
int k;
cin >> k;
vector<int> good(k);
for (int &x : good) {
cin >> x;
--x;
}
if (m == 1) {
cout << 1 << '\n';
continue;
}
vector<unsigned char> alice(2), bob(2);
alice[0] = bob[0] = 0;
alice[1] = bob[1] = 1;
for (int p = 2; p <= n; ++p) {
int sz = 1 << p;
vector<unsigned char> nextAlice(sz), nextBob(sz);
for (int mask = 0; mask < sz; ++mask) {
unsigned char canForceTwo = 0;
unsigned char alwaysTwo = 1;
for (int pos : good) {
if (pos >= p) break;
int nxt = removeBit(mask, pos);
canForceTwo |= bob[nxt];
alwaysTwo &= alice[nxt];
if (canForceTwo && !alwaysTwo) break;
}
nextAlice[mask] = canForceTwo;
nextBob[mask] = alwaysTwo;
}
alice.swap(nextAlice);
bob.swap(nextBob);
}
ll wins = 0;
for (unsigned char v : alice) wins += v;
ll ans = ((1LL << n) + wins) % MOD;
cout << ans << '\n';
}
}#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void setIO() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
}
static int removeBit(int mask, int pos) {
int lower = mask & ((1 << pos) - 1);
int upper = mask >> (pos + 1);
return lower | (upper << pos);
}
int main() {
setIO();
const int MOD = 1000000007;
int T;
cin >> T;
while (T--) {
int n, m;
cin >> n >> m;
int k;
cin >> k;
vector<int> good(k);
for (int &x : good) {
cin >> x;
--x;
}
if (m == 1) {
cout << 1 << '\n';
continue;
}
vector<unsigned char> alice(2), bob(2);
alice[0] = bob[0] = 0;
alice[1] = bob[1] = 1;
for (int p = 2; p <= n; ++p) {
int sz = 1 << p;
vector<unsigned char> nextAlice(sz), nextBob(sz);
for (int mask = 0; mask < sz; ++mask) {
unsigned char canForceTwo = 0;
unsigned char alwaysTwo = 1;
for (int pos : good) {
if (pos >= p) break;
int nxt = removeBit(mask, pos);
canForceTwo |= bob[nxt];
alwaysTwo &= alice[nxt];
if (canForceTwo && !alwaysTwo) break;
}
nextAlice[mask] = canForceTwo;
nextBob[mask] = alwaysTwo;
}
alice.swap(nextAlice);
bob.swap(nextBob);
}
ll wins = 0;
for (unsigned char v : alice) wins += v;
ll ans = ((1LL << n) + wins) % MOD;
cout << ans << '\n';
}
}