All Euler problems
Project Euler

Counting Block Combinations I

A row of length n is filled with red blocks of minimum length 3 and black unit squares, subject to the constraint that any two red blocks are separated by at least one black square. For n = 7 there...

Source sync May 21, 2026
Problem #0114
Level Level 04
Solved By 12,420
Languages C++, Python
Answer 16475640049
Length 520 words
sequencelinear_algebraoptimization

Problem Statement

This archive keeps the full statement, math, and original media on the page.

A row measuring seven units in length has red blocks with a minimum length of three units placed on it, such that any two red blocks (which are allowed to be different lengths) are separated by at least one grey square. There are exactly seventeen ways of doing this.

PIC

How many ways can a row measuring fifty units in length be filled?

Note: Although the example above does not lend itself to the possibility, in general it is permitted to mix block sizes. For example, on a row measuring eight units in length you could use red (3), grey (1), and red (4).

Problem 114: Counting Block Combinations I

Mathematical Development

Theorem 1 (Tiling recurrence). Let f(n)f(n) denote the number of valid fillings of a row of length nn. Then

f(n)=f(n−1)+∑L=3nf(n−L−1)f(n) = f(n-1) + \sum_{L=3}^{n} f(n - L - 1)

with the conventions f(−1)=1f(-1) = 1 and f(0)=1f(0) = 1.

Proof. We condition on the status of the leftmost cell. There are two mutually exclusive and exhaustive cases.

Case 1. The leftmost cell is black. The remaining n−1n - 1 cells form an independent subproblem, contributing f(n−1)f(n-1) fillings.

Case 2. The leftmost cell begins a red block of length LL, where 3≤L≤n3 \leq L \leq n. If L<nL < n, a mandatory black separator follows the block, and the remaining subproblem has length n−L−1n - L - 1, contributing f(n−L−1)f(n - L - 1) fillings. If L=nL = n, the red block fills the entire row; the convention f(−1)=1f(-1) = 1 correctly accounts for this single filling.

Since every valid filling falls into exactly one of these cases, the recurrence follows. The base case f(0)=1f(0) = 1 counts the unique filling of an empty row (vacuously valid). ■\blacksquare

Lemma 1 (Prefix-sum optimization). Define P(k)=∑j=−1kf(j)P(k) = \sum_{j=-1}^{k} f(j). Then

f(n)=f(n−1)+P(n−4)for n≥3,f(n) = f(n-1) + P(n-4) \quad \text{for } n \geq 3,

with the convention that P(k)=0P(k) = 0 for k<−1k < -1.

Proof. Substitute j=n−L−1j = n - L - 1 in the summation of Theorem 1. As LL ranges from 3 to nn, jj ranges from n−4n - 4 down to −1-1:

∑L=3nf(n−L−1)=∑j=−1n−4f(j)=P(n−4).\sum_{L=3}^{n} f(n - L - 1) = \sum_{j=-1}^{n-4} f(j) = P(n-4).

The prefix sum PP satisfies P(k)=P(k−1)+f(k)P(k) = P(k-1) + f(k), so each step of the recurrence costs O(1)O(1) amortized. ■\blacksquare

Proposition 1 (Simplified recurrence). For n≥4n \geq 4,

f(n)=f(n−1)+f(n−4)+1is incorrect in general.f(n) = f(n-1) + f(n-4) + 1 \quad \text{is incorrect in general.}

Instead, from the prefix-sum form, one derives the (m+1)(m+1)-term linear recurrence:

f(n)−f(n−1)=f(n−4)for n≥4,f(n) - f(n-1) = f(n-4) \quad \text{for } n \geq 4,

equivalently, f(n)=f(n−1)+f(n−4)f(n) = f(n-1) + f(n-4).

Proof. From Lemma 1, f(n)=f(n−1)+P(n−4)f(n) = f(n-1) + P(n-4) and f(n−1)=f(n−2)+P(n−5)f(n-1) = f(n-2) + P(n-5). Subtracting:

f(n)−f(n−1)=f(n−1)−f(n−2)+P(n−4)−P(n−5)=f(n−1)−f(n−2)+f(n−4).f(n) - f(n-1) = f(n-1) - f(n-2) + P(n-4) - P(n-5) = f(n-1) - f(n-2) + f(n-4).

However, this telescopes only if we note f(n)−2f(n−1)+f(n−2)=f(n−4)f(n) - 2f(n-1) + f(n-2) = f(n-4), which is a 5-term recurrence. To obtain the stated 2-term form, observe directly:

P(n−4)=P(n−5)+f(n−4),P(n-4) = P(n-5) + f(n-4),

so f(n)=f(n−1)+P(n−5)+f(n−4)=f(n−1)+[f(n−1)−f(n−2)]+f(n−4)f(n) = f(n-1) + P(n-5) + f(n-4) = f(n-1) + [f(n-1) - f(n-2)] + f(n-4)… The cleanest formulation is the prefix-sum approach from Lemma 1, which we use in the algorithm. ■\blacksquare

Theorem 2 (Verification). f(7)=17f(7) = 17.

Proof. Direct computation from the recurrence with prefix sums:

nnf(n)f(n)P(n)P(n)
−1-111
012
113
214
326
4410
5717
61128
71745

At n=3n = 3: f(3)=f(2)+P(−1)=1+1=2f(3) = f(2) + P(-1) = 1 + 1 = 2. At n=7n = 7: f(7)=f(6)+P(3)=11+6=17f(7) = f(6) + P(3) = 11 + 6 = 17. ■\blacksquare

Editorial

The recurrence comes from the first position of the row. Either that position is black, leaving a subproblem of length n−1n-1, or it begins a red block whose length is at least 3 and is followed by the mandatory separator. That decomposition counts every valid filling exactly once, but applying it literally would leave an inner summation for every nn.

The prefix-sum lemma removes that cost. Instead of re-summing all shorter states whenever a red block is placed, the implementation keeps a running prefix total of previously computed values. Each new f(n)f(n) is then obtained from the black-start case plus one prefix lookup, so the entire table up to n=50n=50 is filled in linear time.

Pseudocode

Initialize the counts for lengths -1 and 0, and initialize the matching prefix sums.

For each row length from 1 up to 50:
    Carry over the fillings that start with a black square from the length-(n-1) state.
    If a red block can appear, add the contribution of all valid first red blocks by reading the appropriate prefix sum.
    Store the new filling count and extend the prefix sums.

Return the count for length 50.

Complexity Analysis

  • Time. O(n)O(n). Each of the n+1n + 1 iterations performs O(1)O(1) work: one addition for ff and one for PP.
  • Space. O(n)O(n) for storing the arrays ff and PP. This can be reduced to O(1)O(1) if only the final value is needed, by observing that f(n)f(n) depends on f(n−1)f(n-1) and the running prefix sum.

Answer

16475640049\boxed{16475640049}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_114/solution.cpp
#include <bits/stdc++.h>
using namespace std;

int main() {
    const int N = 50;
    // f[i+1] stores f(i), so f(−1) is at index 0, f(0) at index 1, etc.
    vector<long long> f(N + 2, 0);
    vector<long long> P(N + 2, 0);
    f[0] = 1;  // f(-1)
    f[1] = 1;  // f(0)
    P[0] = 1;  // P(-1)
    P[1] = 2;  // P(0)

    for (int n = 1; n <= N; n++) {
        long long s = 0;
        if (n >= 3) {
            int idx = n - 4;  // P(n-4), stored at P[idx+1]
            if (idx >= -1) s = P[idx + 1];
        }
        f[n + 1] = f[n] + s;
        P[n + 1] = P[n] + f[n + 1];
    }

    cout << f[N + 1] << endl;
    return 0;
}