Problem 114: Counting Block Combinations I
View on Project EulerProject Euler Problem 114 Solution
EulerSolve provides an optimized solution for Project Euler Problem 114, Counting Block Combinations I, with C++, Python, Java, and a step-by-step mathematical explanation.
Problem Summary We have a row of 50 cells. Each cell is either left grey or covered by a red block, and every red block must have length at least 3. If two red blocks both appear, they must be separated by at least one grey cell. The all-grey arrangement is allowed, so the question is to count every valid coloring of the row under those rules. The natural quantity to compute is \(F(n)\), the number of valid arrangements for a row of length \(n\). The goal of the problem is \(F(50)\). Mathematical Approach The key is to choose a state that remembers only the remaining row length. Because every legal continuation depends solely on how many cells are left, dynamic programming gives an exact count with no overcounting. The right state variable Let \(F(n)\) be the number of valid fillings of a row of length \(n\). There is exactly one way to fill an empty row, so $F(0)=1.$ For \(n=1\) and \(n=2\), no red block can fit, so the only arrangement is still the all-grey one: $F(1)=F(2)=1.$ This already captures the most important invariant in the problem: once the left part of the row has been fixed legally, the remainder is just a smaller copy of the same counting problem. A unique decomposition by the leftmost decision To count \(F(n)\), inspect the leftmost cell. If that cell is grey, the rest of the row is any valid arrangement of length \(n-1\), contributing \(F(n-1)\)....
Detailed mathematical approach
Problem Summary
We have a row of 50 cells. Each cell is either left grey or covered by a red block, and every red block must have length at least 3. If two red blocks both appear, they must be separated by at least one grey cell. The all-grey arrangement is allowed, so the question is to count every valid coloring of the row under those rules.
The natural quantity to compute is \(F(n)\), the number of valid arrangements for a row of length \(n\). The goal of the problem is \(F(50)\).
Mathematical Approach
The key is to choose a state that remembers only the remaining row length. Because every legal continuation depends solely on how many cells are left, dynamic programming gives an exact count with no overcounting.
The right state variable
Let \(F(n)\) be the number of valid fillings of a row of length \(n\). There is exactly one way to fill an empty row, so
$F(0)=1.$
For \(n=1\) and \(n=2\), no red block can fit, so the only arrangement is still the all-grey one:
$F(1)=F(2)=1.$
This already captures the most important invariant in the problem: once the left part of the row has been fixed legally, the remainder is just a smaller copy of the same counting problem.
A unique decomposition by the leftmost decision
To count \(F(n)\), inspect the leftmost cell.
If that cell is grey, the rest of the row is any valid arrangement of length \(n-1\), contributing \(F(n-1)\).
If the row starts with a red block of length \(b\), then \(b \ge 3\). Two cases occur:
If \(b=n\), the block fills the whole row, so this contributes exactly 1 arrangement.
If \(b \lt n\), then the next cell must be grey in order to separate this block from any later red block. After placing that mandatory grey separator, the remaining suffix has length \(n-b-1\), contributing \(F(n-b-1)\).
Therefore, for every \(n \ge 1\),
$\boxed{F(n)=F(n-1)+\sum_{b=3}^{n-1} F(n-b-1)+1.}$
The sum is empty when \(n \lt 4\), which is exactly what we want. This recurrence is exhaustive and disjoint: every legal arrangement belongs to exactly one branch, determined by whether the row begins with grey or by the length of its first red block.
Reindexing and a shorter linear recurrence
It is often clearer to rewrite the same formula with the suffix length \(r=n-b-1\):
$F(n)=F(n-1)+1+\sum_{r=0}^{n-4} F(r)\qquad (n \ge 1).$
Subtract the corresponding formula for \(F(n-1)\):
$F(n)-F(n-1)=F(n-1)-F(n-2)+F(n-4)\qquad (n \ge 4).$
So the same counting sequence also satisfies
$\boxed{F(n)=2F(n-1)-F(n-2)+F(n-4)\qquad (n \ge 4),}$
with initial values \(F(0)=1\), \(F(1)=1\), \(F(2)=1\), \(F(3)=2\). The implementations do not need this compressed form, but it is a useful mathematical check that the summation recurrence is internally consistent.
Worked example: a row of length 7
The implementations verify the checkpoint \(F(7)=17\), and the recurrence explains why. First compute the smaller values:
$F(3)=2,\qquad F(4)=4,\qquad F(5)=7,\qquad F(6)=11.$
Now split a row of length 7 by its first move:
$F(7)=F(6)+F(3)+F(2)+F(1)+F(0)+1.$
These terms correspond respectively to: first cell grey; first red block of length 3; length 4; length 5; length 6 followed by one forced grey; and a red block of length 7 filling the whole row. Substituting the known values gives
$F(7)=11+2+1+1+1+1=17.$
This example shows exactly how the recurrence mirrors the physical structure of the row.
How the Code Works
Building the table from shorter rows to longer rows
The C++, Python, and Java implementations store the counts for all row lengths from 0 up to 50 in a one-dimensional table. The entry for length 0 is initialized to 1, and the table is then filled from left to right so that every transition reads only previously computed values.
Evaluating each transition exactly as the combinatorics says
For a fixed row length \(n\), the implementation begins with the contribution \(F(n-1)\), corresponding to a grey first cell. It then loops over every admissible red block length \(b\) from 3 to \(n\). If the block reaches the end of the row, it adds 1. Otherwise it adds the table entry for the remaining suffix of length \(n-b-1\), which already accounts for the mandatory grey separator after that first block.
After finishing all lengths up to 50, the final table entry is the answer. One implementation keeps the row length and minimum block size configurable, while the others hard-code the problem values, but all three evaluate the same recurrence and all three use the same checkpoint \(F(7)=17\).
Complexity Analysis
For a general row length \(N\), the implementation computes \(F(1),F(2),\dots,F(N)\). For each \(n\), it may test every block length from 3 through \(n\), so the running time is
$O\!\left(\sum_{n=1}^{N} n\right)=O(N^2).$
The memory usage is \(O(N)\) because only the one-dimensional dynamic-programming table is stored. In this problem \(N=50\), so the quadratic time bound is tiny in practice.
Footnotes and References
- Problem page: Project Euler 114
- Dynamic programming: Wikipedia - Dynamic programming
- Recurrence relation: Wikipedia - Recurrence relation
- Tiling problem: Wikipedia - Tiling problem