All Euler problems
Project Euler

Paper-strip Game

Two players alternate turns on a strip of n white squares. Each turn, a player picks two contiguous white squares and paints them black. The first player unable to move loses (normal play conventio...

Source sync Apr 19, 2026
Problem #0306
Level Level 13
Solved By 1,446
Languages C++, Python
Answer 852938
Length 354 words
game_theorygraphmodular_arithmetic

Problem Statement

This archive keeps the full statement, math, and original media on the page.

The following game is a classic example of Combinatorial Game Theory:

Two players start with a strip of $n$ white squares and they take alternate turns.

On each turn, a player picks two contiguous white squares and paints them black.

The first player who cannot make a move loses.

  • $n = 1$: No valid moves, so the first player loses automatically.

  • $n = 2$: Only one valid move, after which the second player loses.

  • $n = 3$: Two valid moves, but both leave a situation where the second player loses.

  • $n = 4$: Three valid moves for the first player, who is able to win the game by painting the two middle squares.

  • $n = 5$: Three valid moves for the first player, who is able to win the game by painting the two middle squares.

Problem illustration

So, for $1 \leq n \leq 5$, there are $3$ values of $n$ for which the first player can force a win.

Similarly, for $1 \leq n \leq 50$, there are $40$ values of $n$ for which the first player can force a win.

For $1 \leq n \leq 1000000$, how many values of $n$ are there for which the first player can force a win?

Problem 306: Paper-strip Game

Mathematical Analysis

Sprague-Grundy Theory

When a player paints positions i and i+1 on a strip of length n, the strip decomposes into two independent subgames of lengths i and n-2-i (for i = 0, …, n-2). By the Sprague-Grundy theorem:

G(n)=mex{G(i)G(n2i):0in2}G(n) = \text{mex}\{G(i) \oplus G(n-2-i) : 0 \leq i \leq n-2\}

where mex is the minimum excludant and XOR is the nim-sum.

Base Cases

  • G(0) = 0 (no moves available)
  • G(1) = 0 (no two adjacent squares to mark)
  • G(2) = 1 (one move to empty)

Periodicity

The Grundy values become eventually periodic with period 34 starting at n = 53. This was verified computationally for n up to 200+.

The first player wins if and only if G(n) != 0. Within each period of 34 values (starting at n=53), there are exactly 29 winning positions and 5 losing positions.

Counting Winning Positions

  1. Count wins for n = 1 to 52 directly from computed Grundy values
  2. For n = 53 to 1,000,000: use periodicity
    • 1,000,000 - 53 + 1 = 999,948 values
    • 999,948 / 34 = 29,410 complete periods, remainder 8
    • 29,410 x 29 = 853,090 wins from complete periods
    • Plus wins from the 8 leftover values and the initial segment

Correctness

Theorem. The method described above computes exactly the quantity requested in the problem statement.

Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer. \square

Complexity

  • Time: O(n^2) for computing initial Grundy values (n <= 200), then O(1)
  • Space: O(1) after initial computation

Answer

852938\boxed{852938}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_306/solution.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    // Paper-strip game: count winning positions (G(n) != 0) for 1 <= n <= 10^6
    // Grundy values become periodic with period 34 starting at n=53

    const int LIMIT = 1000000;
    const int COMPUTE_UP_TO = 200;

    vector<int> G(COMPUTE_UP_TO + 1, 0);

    for (int n = 2; n <= COMPUTE_UP_TO; n++) {
        set<int> reachable;
        for (int i = 0; i <= n - 2; i++) {
            reachable.insert(G[i] ^ G[n - 2 - i]);
        }
        int mex = 0;
        while (reachable.count(mex)) mex++;
        G[n] = mex;
    }

    int period = 34;
    int offset = 53;

    // Verify periodicity
    for (int i = offset; i + period <= COMPUTE_UP_TO; i++) {
        assert(G[i] == G[i + period]);
    }

    // Count winning positions (G(n) != 0) for n = 1 to offset-1
    long long count = 0;
    for (int i = 1; i < offset && i <= LIMIT; i++) {
        if (G[i] != 0) count++;
    }

    if (LIMIT >= offset) {
        // Wins per period
        int wins_per_period = 0;
        for (int i = offset; i < offset + period; i++) {
            if (G[i] != 0) wins_per_period++;
        }

        long long remaining = (long long)(LIMIT) - offset + 1;
        long long full_periods = remaining / period;
        long long leftover = remaining % period;

        count += full_periods * wins_per_period;

        for (int i = 0; i < leftover; i++) {
            if (G[offset + i] != 0) count++;
        }
    }

    cout << count << endl;
    return 0;
}