Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

Complete solutions in C++, Python & Java — with step-by-step mathematical explanations

All Problems

Problem 976: XO Game

View on Project Euler

Project Euler Problem 976 Solution

EulerSolve provides an optimized solution for Project Euler Problem 976, XO Game, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Problem 976, XO Game , is solved in the implementations by a pure counting argument modulo \(1234567891\). For the target input \(N=10^7\), the only case that matters is \(N\equiv 0 \pmod 4\), so the game analysis collapses to three explicit combinatorial blocks instead of any game-tree search, dynamic programming, or simulation. The key invariant visible in all three implementations is parity. The final answer is assembled from the number of even-parity game states, corrected by subtracting the even-parity states that are losing and adding the odd-parity states that are winning. Once that split has been proved, the whole problem becomes a sequence of binomial-coefficient summations. Mathematical Approach Let $M=1234567891,\qquad N_o=\frac N2,\qquad N_a=\frac N4.$ For the actual Project Euler input, this means \(N_o=5\cdot 10^6\) and \(N_a=2.5\cdot 10^6\). The closed formulas implemented in C++, Python, and Java are all stated in terms of ordinary binomial coefficients \(\binom{n}{k}\) reduced modulo \(M\). The parity decomposition The code does not carry a recurrence over positions. Instead, the XO game has already been reduced to a parity classification of legal states....

Detailed mathematical approach

Problem Summary

Problem 976, XO Game, is solved in the implementations by a pure counting argument modulo \(1234567891\). For the target input \(N=10^7\), the only case that matters is \(N\equiv 0 \pmod 4\), so the game analysis collapses to three explicit combinatorial blocks instead of any game-tree search, dynamic programming, or simulation.

The key invariant visible in all three implementations is parity. The final answer is assembled from the number of even-parity game states, corrected by subtracting the even-parity states that are losing and adding the odd-parity states that are winning. Once that split has been proved, the whole problem becomes a sequence of binomial-coefficient summations.

Mathematical Approach

Let

$M=1234567891,\qquad N_o=\frac N2,\qquad N_a=\frac N4.$

For the actual Project Euler input, this means \(N_o=5\cdot 10^6\) and \(N_a=2.5\cdot 10^6\). The closed formulas implemented in C++, Python, and Java are all stated in terms of ordinary binomial coefficients \(\binom{n}{k}\) reduced modulo \(M\).

The parity decomposition

The code does not carry a recurrence over positions. Instead, the XO game has already been reduced to a parity classification of legal states. Denote by \(E_{\text{tot}}\) the number of even-parity states, by \(E_{\text{lost}}\) the number of even-parity states that are losing, and by \(O_{\text{win}}\) the number of odd-parity states that are winning. Then the required answer is

$\boxed{\text{Ans}=E_{\text{tot}}-E_{\text{lost}}+O_{\text{win}} \pmod M.}$

This identity is the structural backbone of the solution. Everything else is a closed-form evaluation of these three terms.

Counting all even-parity states

The first block is

$E_{\text{tot}}=\sum_{m=0}^{N_o}\binom{N+2m-1}{2m}.$

For a fixed \(m\), the coefficient \(\binom{N+2m-1}{2m}\) is the standard stars-and-bars count of weak compositions of \(2m\) into \(N\) slots. That is the reason the implementation never enumerates states individually: every even layer indexed by \(2m\) is counted in one shot, and the global total is just the sum over \(m=0,1,\dots,N/2\).

The upper index \(N+2m-1\) is also important computationally. Since \(m\le N/2\), the largest value that ever appears here is \(2N-1\), which explains why the implementations precompute factorial data up to \(2N\).

The correction for even-parity losing states

The losing part of the even layer is not a single uniform family. The formulas split it into one main sum and one separate boundary contribution:

$L_1=\frac12\sum_{r=0}^{N_a}\binom{N_o}{2r}\binom{3N_o-r}{N_o-r},\qquad L_2=\frac12\binom{5N_a}{N_o},$

$E_{\text{lost}}=L_1+L_2.$

The first factor \(\binom{N_o}{2r}\) selects an even-sized distinguished subset inside the half-sized parameter \(N_o\). After that choice is fixed, the second factor \(\binom{3N_o-r}{N_o-r}\) counts the remaining slack combinatorially. So each summand is a product of a parity-constrained choice term and a residual distribution term.

The extra term \(L_2\) shows that the even-loss classification has a special edge family that is not absorbed into the bulk sum. The factor \(1/2\) is not cosmetic: because the modulus is odd and prime, dividing by \(2\) is implemented as multiplication by the modular inverse of \(2\).

The odd-parity winning contribution

The odd layer contributes only through the winning states:

$O_{\text{win}}=\frac12\sum_{r=0}^{N_a-1}\binom{N_o}{2r+1}\binom{3N_o-1-r}{N_o-1-r}.$

This has the same flavor as the previous block, but now the selected subset size is odd, namely \(2r+1\). The second binomial coefficient shifts accordingly from \((3N_o-r,\ N_o-r)\) to \((3N_o-1-r,\ N_o-1-r)\). That shift is exactly what the implementations compute; it is the algebraic signature of passing from the even-loss family to the odd-win family.

The important point is that no recursion remains. By the time the code runs, the game theory has been compressed into a parity split and three one-dimensional sums.

Putting the three blocks together

Substituting the definitions gives the full formula used by the code:

$\boxed{\text{Ans}=\sum_{m=0}^{N_o}\binom{N+2m-1}{2m}-\frac12\sum_{r=0}^{N_a}\binom{N_o}{2r}\binom{3N_o-r}{N_o-r}-\frac12\binom{5N_a}{N_o}+\frac12\sum_{r=0}^{N_a-1}\binom{N_o}{2r+1}\binom{3N_o-1-r}{N_o-1-r}\pmod M.}$

For Problem 976 the heavy work is therefore arithmetic, not search. Once the binomial table is available, the answer is a direct modular accumulation.

Worked Example: the smallest admissible case \(N=4\)

A small case makes the structure concrete. If \(N=4\), then \(N_o=2\) and \(N_a=1\). The three blocks become

$E_{\text{tot}}=\binom30+\binom52+\binom74=1+10+35=46,$

$L_1=\frac12\left(\binom20\binom62+\binom22\binom51\right)=\frac12(15+5)=10,$

$L_2=\frac12\binom52=5,$

$O_{\text{win}}=\frac12\left(\binom21\binom51\right)=5.$

Hence

$\text{Ans}=46-(10+5)+5=36.$

This miniature example uses exactly the same decomposition as the full \(N=10^7\) computation; only the ranges of the sums change.

How the Code Works

Precomputing modular binomial coefficients

The implementations build factorial and inverse-factorial tables up to \(2N\). Because the modulus \(M=1234567891\) is prime, inverse factorials are obtained with Fermat's little theorem: \(a^{-1}\equiv a^{M-2}\pmod M\). After that preprocessing, every \(\binom{n}{k}\) needed by the three formulas is available in constant time.

The C++ implementation also performs a few sanity checks before the large summations begin: it verifies that factorial and inverse-factorial tables multiply back to \(1\), checks some trivial binomial identities, and confirms that the modular inverse of \(2\) behaves correctly.

Evaluating the three summation blocks

The implementation then evaluates the three formula families exactly as written above. One loop runs over \(m=0,\dots,N_o\) for \(E_{\text{tot}}\), one loop runs over \(r=0,\dots,N_a\) for the raw part of \(L_1\), and a third loop runs over \(r=0,\dots,N_a-1\) for the raw part of \(O_{\text{win}}\). The separate boundary term \(L_2\) is a single binomial lookup. Finally, the answer is assembled as

$E_{\text{tot}}-(L_1+L_2)+O_{\text{win}} \pmod M.$

Parallel versus serial execution

The arithmetic is identical in all three languages, but the execution strategy differs slightly. The C++ version slices the long sums into thread ranges manually, the Java version uses parallel streams for the same purpose, and the Python version keeps the loops serial. In every case, the mathematical content is identical: only the scheduling of additions changes.

Complexity Analysis

The dominant preprocessing cost is the factorial table up to \(2N\), so both time and memory for that phase are \(O(N)\). Afterward, the three summations have lengths \(N_o+1\), \(N_a+1\), and \(N_a\), which is still linear in \(N\). There is no hidden quadratic step anywhere in the implementation.

Therefore the overall complexity is \(O(N)\) time and \(O(N)\) memory, with fairly small constant factors once the modular tables are available. For the actual target \(N=10^7\), memory is dominated by the factorial and inverse-factorial arrays, while the remaining state consists only of a few running modular sums.

Footnotes and References

  1. Project Euler problem page: https://projecteuler.net/problem=976
  2. Binomial coefficient: Wikipedia - Binomial coefficient
  3. Stars and bars: Wikipedia - Stars and bars
  4. Modular multiplicative inverse: Wikipedia - Modular multiplicative inverse
  5. Fermat's little theorem: Wikipedia - Fermat's little theorem
  6. Binomial coefficients modulo a prime: cp-algorithms - Binomial coefficients

Mathematical approach · C++ solution · Python solution · Java solution

Previous: Problem 975 · All Project Euler solutions · Next: Problem 977

Need help with a problem? Ask me! 💡
e
✦ Euler GLM 5.2
Hi! I'm Euler. Ask me anything about Project Euler problems, math concepts, or solution approaches! 🧮