All Euler problems
Project Euler

Flea Circus

A 30 x 30 grid starts with exactly one flea on each square. Each round, every flea independently jumps to a uniformly random adjacent square. Corner fleas have 2 choices, edge fleas have 3, and int...

Source sync May 21, 2026
Problem #0213
Level Level 09
Solved By 2,734
Languages C++, Python
Answer 330.721154
Length 383 words
linear_algebraprobabilitygraph

Problem Statement

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

A \(30 \times 30\) grid of squares contains \(900\) fleas, initially one flea per square.

When a bell is rung, each flea jumps to an adjacent square at random (usually \(4\) possibilities, except for fleas on the edge of the grid or at the corners).

What is the expected number of unoccupied squares after \(50\) rings of the bell? Give your answer rounded to six decimal places.

Problem 213: Flea Circus

Mathematical Development

Definition 1. Let G=(V,E)G = (V, E) be the grid graph with

V={(r,c):0≤r,c<30},V = \{(r, c) : 0 \leq r, c < 30\},

and edges between horizontally or vertically adjacent squares. Let N=∣V∣=900N = |V| = 900.

Definition 2. The transition matrix T∈RN×NT \in \mathbb{R}^{N \times N} of the random walk on GG is defined by

Tij={1/deg⁡(i)if j∼i,0otherwise,T_{ij} = \begin{cases} 1/\deg(i) & \text{if } j \sim i, \\ 0 & \text{otherwise}, \end{cases}

where deg⁡(i)∈{2,3,4}\deg(i) \in \{2,3,4\} is the degree of square ii.

Lemma 1 (Row stochasticity). The matrix TT is row-stochastic: ∑jTij=1\sum_j T_{ij} = 1 for all ii.

Proof. For a fixed square ii, exactly deg⁡(i)\deg(i) entries in row ii are nonzero, each equal to 1/deg⁡(i)1/\deg(i). Their sum is therefore 1. □\square

Theorem 1 (Chapman-Kolmogorov). The entry (Tk)ij(T^k)_{ij} equals the probability that a random walk starting at square ii is at square jj after exactly kk steps.

Proof. The case k=1k = 1 is the definition of TT. The inductive step is the usual matrix-product form of the law of total probability. □\square

Theorem 2 (Expected empty squares). Let 1i\mathbf{1}_i be the indicator that square ii is empty after 50 rounds. Then

E ⁣[∑i=1N1i]=∑i=1N∏j=1N(1−(T50)ji).E\!\left[\sum_{i=1}^{N} \mathbf{1}_i\right] = \sum_{i=1}^{N} \prod_{j=1}^{N} \bigl(1 - (T^{50})_{ji}\bigr).

Proof. By linearity of expectation,

E ⁣[∑i1i]=∑iP(square i is empty).E\!\left[\sum_i \mathbf{1}_i\right] = \sum_i P(\text{square } i \text{ is empty}).

Square ii is empty exactly when none of the 900 fleas lands there. Since the fleas move independently,

P(square i is empty)=∏j=1NP(flea j is not at i)=∏j=1N(1−(T50)ji),P(\text{square } i \text{ is empty}) = \prod_{j=1}^{N} P(\text{flea } j \text{ is not at } i) = \prod_{j=1}^{N} \bigl(1 - (T^{50})_{ji}\bigr),

using Theorem 1 for the individual landing probabilities. □\square

Remark. When multiplying many factors of the form 1−p1-p, a logarithmic accumulation

∑jlog⁡(1−pj)\sum_j \log(1-p_j)

can be numerically more stable than repeated floating-point multiplication.

Editorial

The empty-square expectation separates cleanly by square. For one fixed target square, each flea independently misses it with some probability, so the probability that the square ends up empty is a product over all starting squares. Summing those 900 emptiness probabilities gives the final expectation.

The real work is therefore to compute the 50-step distribution of a flea starting from each square. That can be done either by matrix powers or, more practically, by propagating one probability vector for 50 rounds from each starting square. After each source distribution is computed, multiply every board position by the probability that this particular flea is absent there. At the end, those products are exactly the emptiness probabilities.

Pseudocode

Label the 900 squares and precompute the legal neighbors of each one.
Initialize absent_probability[i] = 1 for every square i.

For each starting square s:
    Set a probability vector current with current[s] = 1.

    Repeat 50 times:
        Create a zero vector next.
        For each square u:
            Split current[u] equally among the neighbors of u
            and add those contributions into next.
        Replace current by next.

    For each target square i:
        absent_probability[i] *= 1 - current[i]

answer = sum of absent_probability[i] over all squares i
Return answer

Complexity Analysis

  • Time: Propagating one flea distribution for 50 rounds costs O(50N)O(50N) state updates, so handling all 900 starting squares costs O(N2R)O(N^2 R) with N=900N = 900 and R=50R = 50.
  • Space: O(N)O(N) for the current and next probability vectors, plus O(N)O(N) for the accumulated absence probabilities.

Answer

330.721154\boxed{330.721154}

Code

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

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

int main() {
    const int R = 30, C = 30;
    const int N = R * C;
    const int ROUNDS = 50;

    auto idx = [&](int r, int c) { return r * C + c; };

    int dr[] = {-1, 1, 0, 0};
    int dc[] = {0, 0, -1, 1};

    vector<int> deg(N);
    for (int r = 0; r < R; r++)
        for (int c = 0; c < C; c++) {
            int d = 0;
            for (int k = 0; k < 4; k++)
                if (r + dr[k] >= 0 && r + dr[k] < R && c + dc[k] >= 0 && c + dc[k] < C)
                    d++;
            deg[idx(r, c)] = d;
        }

    vector<double> prod_not(N, 1.0);

    for (int sr = 0; sr < R; sr++) {
        for (int sc = 0; sc < C; sc++) {
            vector<double> cur(N, 0.0), nxt(N, 0.0);
            cur[idx(sr, sc)] = 1.0;

            for (int t = 0; t < ROUNDS; t++) {
                fill(nxt.begin(), nxt.end(), 0.0);
                for (int r = 0; r < R; r++)
                    for (int c = 0; c < C; c++) {
                        int i = idx(r, c);
                        if (cur[i] == 0.0) continue;
                        double p = cur[i] / deg[i];
                        for (int k = 0; k < 4; k++) {
                            int nr = r + dr[k], nc = c + dc[k];
                            if (nr >= 0 && nr < R && nc >= 0 && nc < C)
                                nxt[idx(nr, nc)] += p;
                        }
                    }
                swap(cur, nxt);
            }

            for (int i = 0; i < N; i++)
                prod_not[i] *= (1.0 - cur[i]);
        }
    }

    double answer = 0.0;
    for (int i = 0; i < N; i++)
        answer += prod_not[i];

    cout << fixed << setprecision(6) << answer << endl;
    return 0;
}