Project Euler Problem 409

Let n be a positive integer.

Project Euler Problem 409

Solution

Answer: 253223948

Let

$$q = 2^n.$$

We are counting ordered $n$-tuples

$$(x_1,x_2,\dots,x_n)$$

such that:

  • each $x_i\in{1,2,\dots,q-1}$,
  • all $x_i$ are distinct,
  • the position is winning in nim, i.e.

$$x_1\oplus x_2\oplus \cdots \oplus x_n \ne 0.$$

Define:

  • $A_k$: number of ordered distinct nonzero $k$-tuples,
  • $L_k$: number of ordered distinct nonzero $k$-tuples with xor $0$,
  • $W_k=A_k-L_k$.

We ultimately need $W_n$ for $q=2^n$.


1. Mathematical analysis

Total number of tuples

The total number of ordered distinct nonzero $k$-tuples is simply

$$A_k=(q-1)(q-2)\cdots(q-k)$$

(using falling factorial notation $A_k=(q-1)_k$).

In particular,

$$A_n=(2^n-1)(2^n-2)\cdots(2^n-n).$$


Counting losing positions

We now derive a recurrence for $L_k$.

Consider a losing $k$-tuple:

$$x_1\oplus x_2\oplus\cdots\oplus x_k=0.$$

Then the last element is forced:

$$x_k=x_1\oplus x_2\oplus\cdots\oplus x_{k-1}.$$

So we begin with any ordered distinct nonzero $(k-1)$-tuple and ask when the forced value is valid.

There are $A_{k-1}$ possible $(k-1)$-tuples.

Two bad cases must be excluded.


Bad case 1: the xor is $0$

Then $x_k=0$, forbidden.

This happens exactly when the first $k-1$ entries already xor to $0$.

Count:

$$L_{k-1}.$$


Bad case 2: the xor equals an existing entry

Suppose

$$x_1\oplus\cdots\oplus x_{k-1}=x_i.$$

Then removing $x_i$ leaves a $(k-2)$-tuple whose xor is $0$.

Conversely:

  • choose a losing $(k-2)$-tuple,
  • choose the position $i$ ($k-1$ choices),
  • choose $x_i$ to be any nonzero value not already used.

The $(k-2)$-tuple already uses $k-2$ distinct nonzero values, so there are

$$(q-1)-(k-2)=q-k+1$$

choices for $x_i$.

Therefore the number of tuples in bad case 2 is

$$(k-1)(q-k+1)L_{k-2}.$$


Recurrence

Hence:

$$\boxed{ L_k = A_{k-1} - L_{k-1} - (k-1)(q-k+1)L_{k-2} }$$

with initial conditions

$$L_0=1,\qquad L_1=0.$$

Finally,

$$W_n=A_n-L_n.$$

This recurrence is linear-time and easily fast enough for $n=10^7$.


2. Python implementation

MOD = 1_000_000_007

def solve(n):
    q = pow(2, n, MOD)

    # n = 1 is trivial
    if n == 1:
        return 1

    # A1 = q - 1
    A_prev = (q - 1) % MOD

    # L0 = 1, L1 = 0
    L_prev2 = 1
    L_prev1 = 0

    # Build up to Ln
    for k in range(2, n + 1):

        # Recurrence:
        # Lk = A_{k-1}
        #      - L_{k-1}
        #      - (k-1)(q-k+1)L_{k-2}
        Lk = (
            A_prev
            - L_prev1
            - ((k - 1) * (q - k + 1) % MOD) * L_prev2
        ) % MOD

        # Update A_k from A_{k-1}
        A_curr = (A_prev * (q - k)) % MOD

        # Shift states
        A_prev = A_curr
        L_prev2, L_prev1 = L_prev1, Lk

    # Wn = An - Ln
    return (A_prev - L_prev1) % MOD

print(solve(5))      # 19764360
print(solve(100))    # 384777056
print(solve(10_000_000))

3. Code walkthrough

Step 1

q = pow(2, n, MOD)

Computes $2^n \bmod MOD$.


Step 2

A_prev = q - 1

Stores

$$A_1=q-1.$$


Step 3

L_prev2 = 1
L_prev1 = 0

Initializes:

$$L_0=1,\qquad L_1=0.$$


Step 4

Inside the loop:

Lk = (
    A_prev
    - L_prev1
    - ((k - 1) * (q - k + 1) % MOD) * L_prev2
) % MOD

implements

$$L_k=A_{k-1}-L_{k-1}-(k-1)(q-k+1)L_{k-2}.$$


Step 5

A_curr = (A_prev * (q - k)) % MOD

updates

$$A_k=A_{k-1}(q-k).$$


Step 6

At the end:

(A_prev - L_prev1) % MOD

returns

$$W_n=A_n-L_n.$$


4. Verification

The program reproduces the values given in the statement:

  • $W(1)=1$
  • $W(2)=6$
  • $W(3)=168$
  • $W(5)=19764360$
  • $W(100)\bmod 1,000,000,007=384777056$

all exactly matching the problem statement.

Running the computation for $n=10,000,000$ gives:

$$W(10,000,000)\bmod 1,000,000,007 = 253223948.$$


Answer: 253223948