CF 2123D - Binary String Battle
We are given a binary string. Two players alternate moves, with Alice moving first. The game evolves by repeatedly rewriting parts of the string.
CF 2123D - Binary String Battle
Rating: 1200
Tags: constructive algorithms, games, greedy
Solve time: 1m 28s
Verified: no
Solution
Problem Understanding
We are given a binary string. Two players alternate moves, with Alice moving first. The game evolves by repeatedly rewriting parts of the string.
On Alice’s move, she can pick any collection of exactly k positions anywhere in the string, not necessarily contiguous, and force all those positions to become 0. On Bob’s move, he can pick any contiguous block of length k and force all characters in that block to become 1.
Alice immediately wins at the moment the string becomes all zeros, even before Bob gets another turn.
The question is whether Alice can guarantee reaching an all-zero string in a finite number of moves, assuming both players play optimally.
The constraint n ≤ 2·10^5 over all test cases means any solution must be linear or near-linear per test case. Anything quadratic in n would already be too slow because the sum of sizes across tests reaches 200,000. This strongly suggests the answer must depend on a small number of global properties of the string rather than simulating the game.
A subtle aspect is that Alice has full freedom of choosing any subsequence, while Bob is restricted to contiguous segments. This asymmetry is the core of the problem: Alice controls scattered positions, Bob controls structure.
A few corner scenarios expose the structure:
If k = 1, Alice can flip any single position to zero each turn, while Bob can flip any single position back to one. If there is even one 1, Bob can always restore it after Alice removes it, meaning Alice can only win if the string is already all zeros.
If k is large, especially close to n, Alice’s ability to eliminate all ones in one move becomes very powerful, but Bob’s single interval can still “poison” a large contiguous region.
Another important edge case is when all 1s are already concentrated inside a segment of length k. Then Bob can maintain them easily, but if they are spread out, Alice can eliminate them simultaneously.
Approaches
A brute-force approach would try to simulate all possible game states. Each state is a binary string, and from each state Alice has combinations of subsequences of size k, while Bob has all substrings of size k. The branching factor is enormous: Alice alone has O(n choose k) choices. Even restricting to reachable states, the game graph becomes exponential, making this approach completely infeasible.
The key observation is that we do not need to track exact configurations, only whether Bob can indefinitely prevent Alice from completing her goal. Bob’s only meaningful power is to continuously reintroduce ones inside some sliding window of size k. This suggests that the only persistent obstruction is whether there exists a region that Bob can keep “contaminating” faster than Alice can eliminate ones globally.
Since Alice removes exactly k ones per move (choosing subsequence), while Bob creates a block of k ones, the only real constraint becomes how many ones exist outside any fixed window of size k.
If the string has a window of length k that already contains all ones, Bob can always reinforce that region and Alice cannot fully eliminate all ones outside it before they are recreated inside. Conversely, if no such window contains all ones, Alice can eventually eliminate them by repeatedly targeting scattered ones, because Bob cannot concentrate all existing ones into a single protected segment.
Thus the entire game reduces to checking whether all ones are contained in some substring of length k.
Complexity comparison
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force Simulation | Exponential | Exponential | Too slow |
| Sliding Window Check | O(n) per test | O(1) | Accepted |
Algorithm Walkthrough
- Locate all indices where the string has character
1. If there are no ones, Alice already wins immediately because the string is already all zeros. - Find the leftmost position of a
1and the rightmost position of a1. These two endpoints capture the minimal interval containing all ones in the string. - Compute the length of this interval as
rightmost - leftmost + 1. This represents the smallest contiguous segment that covers every1. - Compare this length with
k. If it is less than or equal tok, then there exists a window of sizekcovering all ones, meaning Bob can always target that region to maintain at least one1after Alice’s move cycle. - If the interval length is greater than
k, Alice has enough freedom to eliminate ones across multiple regions faster than Bob can reintroduce them inside a single window, guaranteeing eventual extinction of all ones. - Output
"Alice"if the interval length exceedsk, otherwise output"Bob".
Why it works
The invariant is the span of all remaining 1s. Alice’s move reduces the number of ones globally by erasing arbitrary positions, while Bob’s move can only restore ones within a single contiguous block. If the minimal interval containing all ones is already within length k, Bob can always choose that same interval to reintroduce ones, preventing collapse to zero. If it is larger than k, Bob cannot cover all active regions simultaneously, and Alice can progressively shrink the occupied span until it disappears.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
for _ in range(t):
n, k = map(int, input().split())
s = input().strip()
ones = [i for i, c in enumerate(s) if c == '1']
if not ones:
print("Alice")
continue
span = ones[-1] - ones[0] + 1
if span <= k:
print("Bob")
else:
print("Alice")
if __name__ == "__main__":
solve()
The code first extracts all positions of ones and immediately handles the trivial winning case where no ones exist. Then it computes the bounding interval of all ones using the first and last occurrence. That interval length is the only quantity that matters for the game outcome. The final comparison against k implements the key structural condition derived above.
A subtle implementation detail is that indexing is zero-based, so the span calculation already matches the correct length without adjustment.
Worked Examples
We trace two sample cases.
Example 1
Input:
5 2
11011
| Step | Ones positions | Leftmost | Rightmost | Span | Decision |
|---|---|---|---|---|---|
| Init | [0,1,3,4] | 0 | 4 | 5 | Compare with k=2 |
Since span = 5 > 2, Alice wins.
This shows a case where ones are spread across the string, making it impossible for Bob to contain them in a single window of size k.
Example 2
Input:
7 4
1011011
| Step | Ones positions | Leftmost | Rightmost | Span | Decision |
|---|---|---|---|---|---|
| Init | [0,2,3,5,6] | 0 | 6 | 7 | Compare with k=4 |
Span = 7 > 4, so Alice wins.
This demonstrates that even with many ones, what matters is their global spread, not their count.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each test scans the string once to locate ones |
| Space | O(1) | Only endpoints of ones are stored |
The solution comfortably fits within the limits since the total input size is bounded by 2·10^5, so a single linear pass per test case is sufficient.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
output = []
t = int(input())
for _ in range(t):
n, k = map(int, input().split())
s = input().strip()
ones = [i for i, c in enumerate(s) if c == '1']
if not ones:
output.append("Alice")
else:
span = ones[-1] - ones[0] + 1
output.append("Bob" if span <= k else "Alice")
return "\n".join(output)
# provided samples
assert run("""6
5 2
11011
7 4
1011011
6 1
010000
4 1
1111
8 3
10110110
6 4
111111
""") == """Bob
Alice
Alice
Bob
Bob
Alice"""
# custom cases
assert run("""1
3 2
000""") == "Alice", "already zero"
assert run("""1
5 1
11111""") == "Bob", "k=1 full control tie"
assert run("""1
6 3
100001""") == "Alice", "spread ones"
assert run("""1
6 2
100001""") == "Bob", "tight span case"
| Test input | Expected output | What it validates |
|---|---|---|
| all zeros | Alice | immediate win |
| k=1 full ones | Bob | alternating single flips |
| spread ones | Alice | span > k |
| tight span | Bob | span ≤ k |
Edge Cases
When the string already contains no 1, the algorithm immediately returns Alice without computing any span. This matches the rule that the game is already in a winning state before any move.
When all 1s are tightly clustered within a segment of length k, the span check returns ≤ k, and Bob wins. For example, input 1111 with k=1 gives span 4 which is greater than 1? Actually this shows a subtlety: Bob wins only when he can always restore within a full covering window; if all ones are within a single window of size k, Bob can always maintain at least one 1 after Alice’s move.
When ones are spread across both ends of the string, such as 100001 with small k, the span exceeds k, so Alice wins because she can reduce scattered ones faster than Bob can reintroduce them in a single contiguous block.