Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

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

All Problems

Problem 114: Counting Block Combinations I

View on Project Euler

Project 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

  1. Problem page: Project Euler 114
  2. Dynamic programming: Wikipedia - Dynamic programming
  3. Recurrence relation: Wikipedia - Recurrence relation
  4. Tiling problem: Wikipedia - Tiling problem

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

Previous: Problem 113 · All Project Euler solutions · Next: Problem 115

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