All Euler problems
Project Euler

Robot Walks

A robot moves on a plane, taking steps that are each an arc of one fifth of a circle. At each step, the robot can turn either left or right. After 70 steps, how many distinct closed paths return th...

Source sync May 21, 2026
Problem #0208
Level Level 10
Solved By 2,065
Languages C++, Python
Answer 331951449665644800
Length 400 words
dynamic_programmingmodular_arithmeticgeometry

Problem Statement

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

A robot moves in a series of one-fifth circular arcs (\(72^\circ \)), with a free choice of a clockwise or an anticlockwise arc for each step, but no turning on the spot.

One of \(70932\) possible closed paths of \(25\) arcs starting northward is

PIC

Given that the robot starts facing North, how many journeys of \(70\) arcs in length can it take that return it, after the final arc, to its starting position?

(Any arc may be traversed multiple times.)

Problem 208: Robot Walks

Mathematical Development

Heading States

Each step changes the heading by ±72∘\pm 72^\circ, so the heading can be represented by an element of Z/5Z\mathbb{Z}/5\mathbb{Z}:

  • left turn: d↦d+1(mod5)d \mapsto d + 1 \pmod{5}
  • right turn: d↦d−1(mod5)d \mapsto d - 1 \pmod{5}

The walk starts at heading 00 and must end there as well.

Equal Use of the Five Headings

Let vdv_d be the number of steps taken while the robot is in heading dd. Writing the arc displacements in complex form and summing them shows that the total displacement is a fixed nonzero scalar multiple of

v0+v1ω+v2ω2+v3ω3+v4ω4,v_0 + v_1 \omega + v_2 \omega^2 + v_3 \omega^3 + v_4 \omega^4,

where ω=e2πi/5\omega = e^{2\pi i / 5}.

The only rational linear relation among 1,ω,ω2,ω3,ω41, \omega, \omega^2, \omega^3, \omega^4 is

1+ω+ω2+ω3+ω4=0,1 + \omega + \omega^2 + \omega^3 + \omega^4 = 0,

so zero displacement forces

v0=v1=v2=v3=v4.v_0 = v_1 = v_2 = v_3 = v_4.

Since the walk has 70 steps in total, each heading must therefore be used exactly

70/5=1470 / 5 = 14

times.

Dynamic Programming State

Once the geometric condition has been reduced to equal heading counts, the remaining task is purely combinatorial. We use memoized dynamic programming on states

(d,c0,c1,c2,c3,c4),(d, c_0, c_1, c_2, c_3, c_4),

where dd is the current heading and cic_i is the number of remaining times heading ii may still be used.

If all cic_i are zero, the walk is valid exactly when the current heading is back to 00.

Transitions

From a state with cd>0c_d > 0, we consume one use of heading dd and branch in the two possible directions:

  • turn left and move to heading (d+1) mod 5(d+1) \bmod 5
  • turn right and move to heading (d−1) mod 5(d-1) \bmod 5

Every closed walk corresponds to exactly one path through this state graph, and every path that exhausts the counts and returns to heading 0 is a valid closed walk.

Editorial

The geometry looks awkward until it is compressed into a counting constraint. A closed walk must use the five heading classes equally often, so for 70 steps each heading is used exactly 14 times. After that reduction, the shape of the path no longer needs separate coordinate tracking.

What remains is a finite state search. At any moment we only need to know the current heading and how many uses of each heading are still available. From that state there are at most two legal continuations, corresponding to the next left or right turn, so memoization counts the closed walks without revisiting the same subproblem.

Pseudocode

Define Solve(heading, c0, c1, c2, c3, c4):
    remaining = c0 + c1 + c2 + c3 + c4
    If remaining = 0:
        return 1 if heading = 0, otherwise 0

    If the state was computed before:
        return the cached value

    Let counts be [c0, c1, c2, c3, c4].
    If counts[heading] = 0:
        return 0

    Decrease counts[heading] by 1.

    total =
        Solve((heading + 1) mod 5, updated counts) +
        Solve((heading - 1) mod 5, updated counts)

    Cache total for the state and return it.

Return Solve(0, 14, 14, 14, 14, 14)

Complexity Analysis

  • Time: O(number of reachable memoized states)O(\text{number of reachable memoized states}), with at most two transitions per state.
  • Space: O(number of reachable memoized states)O(\text{number of reachable memoized states}) for the cache.

Answer

331951449665644800\boxed{331951449665644800}

Code

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

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

// State: (heading d, c0, c1, c2, c3) where ci = remaining visits for heading i
// c4 = remaining - c0 - c1 - c2 - c3 (but we track all 5)
// Each heading gets exactly 14 visits. Total steps = 70.

// We encode state as (d, c0, c1, c2, c3) since c4 = total_remaining - c0 - c1 - c2 - c3
// But it's easier to track all 5 counts.

// State: d * 15^4 + c0 * 15^3 + c1 * 15^2 + c2 * 15 + c3
// c4 is deduced from remaining steps = c0+c1+c2+c3+c4 and total = 70

// Actually let's use a map or a flat array.
// 5 * 15^4 = 253125 states (small enough for array)
// But we need c4 too. With c4 deducible: remaining = 70 - steps_taken
// c4 = remaining - c0 - c1 - c2 - c3... no, c0..c4 ARE the remaining counts.
// So c4 = (total remaining for heading 4).
// We don't know c4 from just c0..c3 unless we track steps taken.
// Actually: initially c0=c1=c2=c3=c4=14. As we take steps, ci decreases.
// c4 is independent of c0..c3. So we need all 5.
//
// State: (d, c0, c1, c2, c3, c4) -> 5 * 15^5 = 3,796,875 states. Still fine.
//
// Or we note that at each step from heading d, cd decreases by 1.
// So we need cd > 0 to take a step.

// Let's use memoization with a map for simplicity, or pack the state into an integer.
// Pack: d + 5*(c0 + 15*(c1 + 15*(c2 + 15*(c3 + 15*c4))))
// Max index: 5 * 15^5 = 3796875

long long dp[5 * 15 * 15 * 15 * 15 * 15]; // ~30 MB, feasible
bool visited[5 * 15 * 15 * 15 * 15 * 15];

inline int encode(int d, int c0, int c1, int c2, int c3, int c4) {
    return d + 5*(c0 + 15*(c1 + 15*(c2 + 15*(c3 + 15*c4))));
}

long long solve(int d, int c0, int c1, int c2, int c3, int c4) {
    int remaining = c0 + c1 + c2 + c3 + c4;
    if (remaining == 0) {
        return (d == 0) ? 1 : 0;
    }

    int idx = encode(d, c0, c1, c2, c3, c4);
    if (visited[idx]) return dp[idx];
    visited[idx] = true;

    int c[5] = {c0, c1, c2, c3, c4};
    long long result = 0;

    if (c[d] > 0) {
        c[d]--;
        // Left: heading becomes (d+1) % 5
        int nd = (d + 1) % 5;
        result += solve(nd, c[0], c[1], c[2], c[3], c[4]);
        // Right: heading becomes (d-1+5) % 5
        nd = (d + 4) % 5;
        result += solve(nd, c[0], c[1], c[2], c[3], c[4]);
        c[d]++;
    }

    dp[idx] = result;
    return result;
}

int main() {
    memset(visited, false, sizeof(visited));
    cout << solve(0, 14, 14, 14, 14, 14) << endl;
    return 0;
}