Open Source · 7 Languages · 900+ Problems

Project Euler Solutions

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

All Problems

Problem 115: Counting Block Combinations II

View on Project Euler

Project Euler Problem 115 Solution

EulerSolve provides an optimized solution for Project Euler Problem 115, Counting Block Combinations II, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Let \(F(m,n)\) be the number of ways to fill a row of length \(n\) with black squares and red blocks of length at least \(m\), under the rule that any two red blocks must be separated by at least one black square. The all-black row is allowed and must be counted. Problem 115 fixes \(m=50\) and asks for the smallest row length \(n\) such that \(F(50,n) \gt 1{,}000{,}000\). This is the same fill-count function that appears in the previous problem, but the role of \(n\) changes: instead of evaluating one fixed row length, we must understand how the count grows and detect the first row where it crosses the threshold. Mathematical Approach Write \(F_m(n)=F(m,n)\). The key is to count arrangements by looking at what happens at the left end of the row. That point of view leads directly to the recurrence used by the implementations. The Fill-Count Function If \(0 \le n \lt m\), no red block fits at all, so the only legal arrangement is the all-black row. Therefore $F_m(0)=1,\qquad F_m(n)=1 \text{ for } 1 \le n \lt m.$ Those values are the first invariant of the problem: before the row is long enough to hold a red block, the count stays at 1. A Left-End Decomposition Now assume \(n \ge m\). A legal arrangement of length \(n\) falls into two disjoint classes. First, the row may begin with a black square....

Detailed mathematical approach

Problem Summary

Let \(F(m,n)\) be the number of ways to fill a row of length \(n\) with black squares and red blocks of length at least \(m\), under the rule that any two red blocks must be separated by at least one black square. The all-black row is allowed and must be counted. Problem 115 fixes \(m=50\) and asks for the smallest row length \(n\) such that \(F(50,n) \gt 1{,}000{,}000\).

This is the same fill-count function that appears in the previous problem, but the role of \(n\) changes: instead of evaluating one fixed row length, we must understand how the count grows and detect the first row where it crosses the threshold.

Mathematical Approach

Write \(F_m(n)=F(m,n)\). The key is to count arrangements by looking at what happens at the left end of the row. That point of view leads directly to the recurrence used by the implementations.

The Fill-Count Function

If \(0 \le n \lt m\), no red block fits at all, so the only legal arrangement is the all-black row. Therefore

$F_m(0)=1,\qquad F_m(n)=1 \text{ for } 1 \le n \lt m.$

Those values are the first invariant of the problem: before the row is long enough to hold a red block, the count stays at 1.

A Left-End Decomposition

Now assume \(n \ge m\). A legal arrangement of length \(n\) falls into two disjoint classes.

First, the row may begin with a black square. Removing that first square leaves any valid arrangement of length \(n-1\), so this case contributes \(F_m(n-1)\).

Second, the row may begin with a red block of length \(b\), where \(m \le b \le n\). If \(b=n\), the whole row is one red block, contributing exactly 1 arrangement. If \(b \lt n\), the separator rule forces the next cell to be black, leaving a residual row of length \(n-b-1\). That contributes \(F_m(n-b-1)\).

So for \(n \ge m\) we obtain the recurrence that the C++, Python, and Java implementations compute directly:

$F_m(n)=F_m(n-1)+\sum_{b=m}^{n}\begin{cases}1,& b=n,\\\\ F_m(n-b-1),& b \lt n.\end{cases}$

Equivalently, separating the full-row block from the rest gives

$F_m(n)=F_m(n-1)+\sum_{b=m}^{n-1}F_m(n-b-1)+1.$

Reindexing the Sum

The block-length sum becomes clearer if we substitute \(r=n-b-1\). Then \(r\) runs from \(0\) up to \(n-m-1\), so

$F_m(n)=F_m(n-1)+1+\sum_{r=0}^{n-m-1}F_m(r)\qquad (n \ge m).$

This form shows the structure nicely: every new row length inherits all arrangements of length \(n-1\), adds the single full-row red block, and also adds every earlier arrangement that can sit behind a leading red block and one mandatory separator.

If one subtracts the same formula for \(n-1\), the sum collapses and yields a shorter linear recurrence:

$F_m(n)=2F_m(n-1)-F_m(n-2)+F_m(n-m-1)\qquad (n \ge m+1).$

The implementations do not need this compressed form, but it is a useful consequence of the same counting argument.

Worked Example: \(m=3\), \(n=7\)

A concrete small example makes the recurrence easy to trust. For minimum red length \(m=3\), the formula gives

$F_3(7)=F_3(6)+F_3(3)+F_3(2)+F_3(1)+F_3(0)+1.$

Using the earlier values \(F_3(6)=11\), \(F_3(3)=2\), and \(F_3(2)=F_3(1)=F_3(0)=1\), we get

$F_3(7)=11+2+1+1+1+1=17.$

The terms have a direct interpretation: the row begins with a black square, or with a red block of length \(3\), \(4\), \(5\), \(6\), or \(7\). Whenever the block does not fill the row, one black separator is forced before the remainder.

Why an Increasing Search Finds the Answer

The function \(F_m(n)\) is monotone in \(n\). Every valid arrangement of length \(n-1\) becomes a valid arrangement of length \(n\) by prefixing one black square, so \(F_m(n) \ge F_m(n-1)\). Once \(n \ge m\), there is also at least one genuinely new arrangement, namely a single red block of length \(n\).

For the target case \(m=50\), the growth starts slowly because no red block fits before length 50:

$F_{50}(0)=1,\quad F_{50}(49)=1,\quad F_{50}(50)=2,\quad F_{50}(51)=4.$

Continuing the recurrence shows that the threshold is crossed between 167 and 168:

$F_{50}(167)=978181,\qquad F_{50}(168)=1053389.$

Therefore the least \(n\) with \(F(50,n) \gt 10^6\) is \(168\).

How the Code Works

Building One Fill-Count Table

The C++, Python, and Java implementations all build a one-dimensional dynamic-programming table for a fixed row length. The entry for length \(n\) begins with the count for \(n-1\), representing a leading black square. Then the implementation tries every admissible red block length from \(m\) to \(n\). A full-row block contributes 1; a shorter block contributes the table entry for the remaining suffix after subtracting the block and the mandatory separator.

Because the table is filled from shorter rows to longer rows, every quantity on the right-hand side has already been computed when it is needed. That is the core invariant that makes the bottom-up approach correct.

Searching for the First Value Above the Target

After defining the counting routine, the implementation checks row lengths \(n=0,1,2,\dots\) in increasing order and stops at the first one whose fill count exceeds the target. Since the counts are monotone, the first hit is the desired answer. A standard sanity check is the warm-up case \(m=3\), where the first value above one million occurs at \(n=30\); the target instance simply repeats the same search with \(m=50\).

Complexity Analysis

For one fixed length \(L\), building the table up to \(L\) costs

$\sum_{n=1}^{L}\max(0,n-m+1)=O(L^2)$

time, because each state scans all feasible red block lengths. The memory usage is \(O(L)\) for the dynamic-programming table.

The implementations then repeat that computation for \(L=0,1,2,\dots,N\) until the threshold is first exceeded at \(N=168\). In asymptotic terms, that makes the total running time \(O(N^3)\) with \(O(N)\) peak memory for the code exactly as written. For this problem size the computation is still tiny, which is why the straightforward implementation is perfectly adequate.

Footnotes and References

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

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

Previous: Problem 114 · All Project Euler solutions · Next: Problem 116

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