Project Euler Problem 215

Consider the problem of building a wall out of 2 times 1 and 3 times 1 bricks (text{horizontal} times text{vertical} dim

Project Euler Problem 215

Solution

Answer: 806844323190414

Let $W(w,h)$ denote the number of crack-free walls of width $w$ and height $h$.

We must compute $W(32,10)$.

The key observation is:

  • A row is determined entirely by the positions of its internal vertical cracks.
  • Two rows are compatible iff they share no crack positions.
  • Thus the problem becomes:
  1. Generate all valid rows of width $32$,
  2. Build a compatibility graph,
  3. Count length-10 paths in that graph.

Step 1 — Representing a row

Bricks are $2\times1$ and $3\times1$, placed horizontally.

A row of width $w$ is any composition of $w$ using 2s and 3s.

Example for width $9$:

  • $2+2+2+3$
  • $3+3+3$
  • $2+3+2+2$

etc.

For each row, record the crack positions between bricks.

Example:

$$2+3+2+2$$

has cumulative sums

$$2,5,7,9$$

Ignoring the wall edge at $9$, the crack set is

$${2,5,7}$$

Two rows are compatible iff their crack sets are disjoint.


Step 2 — Number of possible rows

Let $R(w)$ be the number of rows of width $w$.

Then

$$R(w)=R(w-2)+R(w-3)$$

with:

$$R(0)=1,\quad R(1)=0,\quad R(2)=1,\quad R(3)=1$$

For $w=32$, this yields:

$$R(32)=3329$$

So there are only 3329 row types — small enough for graph methods.


Step 3 — Compatibility graph

Create a graph:

  • each node = one row pattern,
  • edge between nodes if no cracks align.

If row $i$ is compatible with row $j$, then they may be stacked consecutively.

We now want the number of height-10 stacks.

This is equivalent to counting walks of length $9$ in the compatibility graph.


Step 4 — Dynamic programming

Let:

$$dp_k(i)$$

be the number of crack-free walls of height $k$ ending with row $i$.

Initialize:

$$dp_1(i)=1$$

Then:

$$dp_{k+1}(j)=\sum_{i\sim j} dp_k(i)$$

where $i\sim j$ means compatible.

Finally:

$$W(32,10)=\sum_i dp_{10}(i)$$


Python implementation

from collections import defaultdict

WIDTH = 32
HEIGHT = 10

# ---------------------------------------------------------
# Generate all possible rows
# Each row is represented by a tuple of crack positions
# ---------------------------------------------------------

rows = []

def build(pos, cracks):
    """
    Recursively build rows of total width WIDTH.

    pos     = current filled width
    cracks  = crack positions so far
    """
    if pos == WIDTH:
        rows.append(tuple(cracks))
        return

    # place a 2-brick
    if pos + 2 <= WIDTH:
        new_pos = pos + 2
        build(
            new_pos,
            cracks + ([new_pos] if new_pos < WIDTH else [])
        )

    # place a 3-brick
    if pos + 3 <= WIDTH:
        new_pos = pos + 3
        build(
            new_pos,
            cracks + ([new_pos] if new_pos < WIDTH else [])
        )

build(0, [])

# ---------------------------------------------------------
# Build compatibility graph
# Two rows are compatible if they share no cracks
# ---------------------------------------------------------

n = len(rows)

compat = [[] for _ in range(n)]

crack_sets = [set(r) for r in rows]

for i in range(n):
    for j in range(i + 1, n):
        if crack_sets[i].isdisjoint(crack_sets[j]):
            compat[i].append(j)
            compat[j].append(i)

# ---------------------------------------------------------
# DP over wall height
# dp[i] = number of ways ending with row i
# ---------------------------------------------------------

dp = [1] * n   # height = 1

for _ in range(HEIGHT - 1):
    new_dp = [0] * n

    for i in range(n):
        for j in compat[i]:
            new_dp[j] += dp[i]

    dp = new_dp

answer = sum(dp)

print(answer)

Code walkthrough

Row generation

The recursive function:

build(pos, cracks)

tries adding either:

  • a brick of width 2,
  • or a brick of width 3.

Whenever we finish exactly at width 32, we store the crack positions.

Example:

2 + 3 + 2 + 2

produces:

(2, 5, 7)

Compatibility test

We convert each crack tuple into a set.

Two rows are compatible if:

setA.isdisjoint(setB)

meaning no cracks coincide.


Dynamic programming

Initially every row can be the first layer:

dp = [1] * n

For each additional layer:

new_dp[j] += dp[i]

whenever rows $i$ and $j$ are compatible.

After 10 layers, summing all possibilities gives the answer.


Verification on the small example

For the example in the problem:

$$W(9,3)=8$$

Changing:

WIDTH = 9
HEIGHT = 3

the program returns:

8

matching the statement.


Final verification

  • Number of row states: $3329$, computationally reasonable.
  • Compatibility graph is sparse.
  • DP counts all valid height-10 stacks exactly once.
  • The known published Project Euler value matches the computation.

Answer: 806844323190414