Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

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

All Problems

Problem 628: Open Chess Positions

View on Project Euler

Project Euler Problem 628 Solution

EulerSolve provides an optimized solution for Project Euler Problem 628, Open Chess Positions, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary On an \(n\times n\) board, exactly one square is blocked in every row and exactly one square is blocked in every column. So every board is determined by a permutation \(p\in S_n\), where the blocked square in row \(i\) lies in column \(p_i\). A position is called open if there exists a path from the top-left square to the bottom-right square using only right and down moves and never stepping on a blocked square. If \(f(n)\) denotes the number of open boards, the task is to compute \(f(10^8)\) modulo \(1008691207\). The important point is that the implementations do not search for paths on the huge board. They use an exact combinatorial identity that reduces the whole problem to factorials and a prefix sum of factorials. Mathematical Approach Let \(f(n)\) be the number of open permutation boards of size \(n\). Since every placement is a permutation, there are \(n!\) total configurations. The mathematical job is therefore to understand which permutations force every monotone path to fail. Step 1: Encode the Board as a Permutation Choose a permutation \(p=(p_1,\dots,p_n)\). The blocked cells are then $ (1,p_1),(2,p_2),\dots,(n,p_n). $ Because the blocked columns are all distinct, the board is a permutation matrix of forbidden cells. This observation removes the need to think about arbitrary obstacle patterns....

Detailed mathematical approach

Problem Summary

On an \(n\times n\) board, exactly one square is blocked in every row and exactly one square is blocked in every column. So every board is determined by a permutation \(p\in S_n\), where the blocked square in row \(i\) lies in column \(p_i\). A position is called open if there exists a path from the top-left square to the bottom-right square using only right and down moves and never stepping on a blocked square.

If \(f(n)\) denotes the number of open boards, the task is to compute \(f(10^8)\) modulo \(1008691207\). The important point is that the implementations do not search for paths on the huge board. They use an exact combinatorial identity that reduces the whole problem to factorials and a prefix sum of factorials.

Mathematical Approach

Let \(f(n)\) be the number of open permutation boards of size \(n\). Since every placement is a permutation, there are \(n!\) total configurations. The mathematical job is therefore to understand which permutations force every monotone path to fail.

Step 1: Encode the Board as a Permutation

Choose a permutation \(p=(p_1,\dots,p_n)\). The blocked cells are then

$ (1,p_1),(2,p_2),\dots,(n,p_n). $

Because the blocked columns are all distinct, the board is a permutation matrix of forbidden cells. This observation removes the need to think about arbitrary obstacle patterns. The geometry is rigid enough that the counting problem can be translated into a combinatorial question about permutations.

A monotone path uses only right and down moves, so only the relative order of the blocked cells matters. That is the reason a closed formula exists at all.

Step 2: Switch from Path Search to Barrier Counting

It is more convenient to think about closed boards, where every monotone path is blocked. Those closed boards can be organized by the depth of the first effective barrier that separates the reachable northwest region from the unreachable southeast region.

The simplest closed families occur near the corners: the starting square may already be blocked, the ending square may already be blocked, or a very shallow corner barrier may immediately cut off all progress. After those easy cases, the remaining closed boards are indexed by an interior barrier depth. Once that depth is fixed, the leftover rows and columns can be permuted freely, which is why factorial terms dominate the final answer.

Step 3: Use the Exact Counting Identity

The C++, Python, and Java implementations all evaluate the same exact identity:

$ f(n)=n!-2(n-1)!-(n-2)!-(n-3)!+2+(n-3)\sum_{k=0}^{n-4} k! \qquad (n\ge 3), $

together with

$ f(n)=0 \qquad (n\le 2). $

The large negative factorial terms remove the dominant closed families. The constant correction \(2\) and the tail term \((n-3)\sum_{k=0}^{n-4}k!\) add back the configurations that were removed too often when the barrier families overlap. For the actual computation, this identity is the whole story: once it is known, there is no need to inspect individual paths.

Step 4: Reduce the Problem to Four Factorials and One Prefix Sum

Define the factorial prefix sum

$ S_m=\sum_{k=0}^{m} k!. $

Then the formula becomes

$ f(n)=n!-2(n-1)!-(n-2)!-(n-3)!+2+(n-3)S_{n-4}\pmod{1008691207}. $

So the entire computation consists of two tasks:

$ \text{compute } S_{n-4}, \qquad \text{and compute } (n-3)!, (n-2)!, (n-1)!, n! \pmod{1008691207}. $

This is exactly why the implementations can stay linear and use constant extra memory.

Worked Example: \(n=5\)

For \(n=5\), the prefix sum is

$ S_1=0!+1!=1+1=2. $

Substituting into the exact identity gives

$ \begin{aligned} f(5) &=5!-2\cdot 4!-3!-2!+2+2(0!+1!) \\ &=120-48-6-2+2+4 \\ &=70. \end{aligned} $

This matches the standard small checkpoint. The same formula also gives \(f(3)=2\), which is the smallest nontrivial open case.

How the Code Works

The implementations treat the problem as pure modular arithmetic. They first handle the small base case \(n\le 2\), where the answer is \(0\). For \(n\ge 3\), they maintain one running factorial and one running sum of factorials.

The first pass accumulates

$ 0!+1!+\cdots+(n-4)! \pmod{1008691207}. $

Then the same running factorial is continued through the last four multiplications so that the implementation obtains \((n-3)!\), \((n-2)!\), \((n-1)!\), and \(n!\) modulo the given prime. Those values are substituted directly into the closed formula, and the result is normalized back into the standard modular range.

The C++ implementation also performs a small brute-force verification for tiny \(n\): it enumerates permutations, checks path existence with a grid dynamic program, and confirms that the formula matches exact counting before evaluating the large target.

Complexity Analysis

The running time is \(O(n)\) modular multiplications, because the algorithm makes one forward pass from \(1\) up to \(n\). The extra memory usage is \(O(1)\), since only a few integers are stored for the current factorial, the factorial prefix sum, and the final expression. That scaling is exactly what makes \(n=10^8\) practical.

Footnotes and References

  1. Problem page: https://projecteuler.net/problem=628
  2. Permutation matrix: Wikipedia - Permutation matrix
  3. Lattice path: Wikipedia - Lattice path
  4. Inclusion-exclusion principle: Wikipedia - Inclusion-exclusion principle
  5. Factorial: Wikipedia - Factorial

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

Previous: Problem 627 · All Project Euler solutions · Next: Problem 629

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! 🧮