All Euler problems
Project Euler

Heighway Dragon

The Heighway Dragon curve is defined by the Lindenmayer system: Axiom: F_a Rules: F_a -> F_a R F_b, F_b -> F_a L F_b where F_a and F_b mean "draw forward", R means "turn right 90 degrees", and L me...

Source sync Apr 19, 2026
Problem #0220
Level Level 09
Solved By 2,458
Languages C++, Python
Answer 139776,963904
Length 334 words
modular_arithmeticrecursionbrute_force

Problem Statement

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

Let \(D_0\) be the two-letter string "Fa". For \(n \geq 1\), derive Dn from \(D_{n - 1}\) by the string-rewriting rules:

  • "a" â’ "aRbFR"

  • "b" â’ "LFaLb"

Thus, \(D_0 =\) "Fa", \(D_1 = \) "FaRbFR", \(D_2 = \) "FaRbFRRLFaLbFR", and so on.

These strings can be interpreted as instructions to a computer graphics program, with "F" meaning "draw forward one unit", "L" meaning "turn left \(90\) degrees", "R" meaning "turn right 90 degrees", and "a" and "b" being ignored. The initial position of the computer cursor is \((0,0)\), pointing up towards \((0,1)\).

Then Dn is an exotic drawing known as the Heighway Dragon of order n. For example, D10 is shown below; counting each "F" as one step, the highlighted spot at \((18,16)\) is the position reached after \(500\) steps.

PIC

What is the position of the cursor after \(1012\) steps in \(D_{50}\) ?

Give your answer in the form \(x,y\) with no spaces.

Problem 220: Heighway Dragon

Mathematical Analysis

String Expansion

After nn iterations, the string DnD_n has 2n2^n draw commands (FaF_a or FbF_b). The expansion is:

  • D0=FaD_0 = F_a
  • Dn=Dn1(a)  R  Dn1(b)D_n = D_{n-1}(a) \; R \; D_{n-1}(b)

where Dn1(a)D_{n-1}(a) is the expansion with FaF_a as starting symbol and Dn1(b)D_{n-1}(b) with FbF_b.

Actually, the rules are:

  • FaFaRFbF_a \to F_a R F_b
  • FbFaLFbF_b \to F_a L F_b

After nn expansions starting from FaF_a, we get a string with 2n2^n forward moves.

Recursive Computation

The key insight is that we can compute the position after kk steps recursively without expanding the full string.

Define for each expansion level nn and starting symbol s{a,b}s \in \{a, b\}:

  • The total number of forward steps: steps(n)=2n\text{steps}(n) = 2^n
  • The net displacement (Δx,Δy)(\Delta x, \Delta y) when starting in a given direction.
  • The net rotation (total turning).

For the expansion of FaF_a at level nn: Fa(n)=Fa(n1)RFb(n1)F_a^{(n)} = F_a^{(n-1)} R F_b^{(n-1)}

If we need kk steps from Fa(n)F_a^{(n)}:

  • If k2n1k \leq 2^{n-1}: take kk steps from Fa(n1)F_a^{(n-1)}.
  • If k>2n1k > 2^{n-1}: take all 2n12^{n-1} steps from Fa(n1)F_a^{(n-1)}, turn right, then take k2n1k - 2^{n-1} steps from Fb(n1)F_b^{(n-1)}.

Direction Tracking

We track the current direction as one of {N, E, S, W} encoded as 0, 1, 2, 3. Turning right adds 1 mod 4; turning left subtracts 1 mod 4.

We precompute for each level nn and symbol ss:

  • dx(n,s,d)\text{dx}(n, s, d), dy(n,s,d)\text{dy}(n, s, d): net displacement when starting in direction dd.
  • rot(n,s)\text{rot}(n, s): net rotation (number of right turns mod 4).

Base case (n=0n = 0): FaF_a and FbF_b both just move forward 1 step in the current direction. Net rotation = 0.

Recursion for Precomputation

For Fa(n)=Fa(n1)  R  Fb(n1)F_a^{(n)} = F_a^{(n-1)} \; R \; F_b^{(n-1)} starting in direction dd:

  • After Fa(n1)F_a^{(n-1)}: direction becomes d+rot(n1,a)d + \text{rot}(n-1, a).
  • After R: direction becomes d+rot(n1,a)+1d + \text{rot}(n-1, a) + 1.
  • After Fb(n1)F_b^{(n-1)}: direction becomes d+rot(n1,a)+1+rot(n1,b)d + \text{rot}(n-1, a) + 1 + \text{rot}(n-1, b).

So rot(n,a)=rot(n1,a)+1+rot(n1,b)(mod4)\text{rot}(n, a) = \text{rot}(n-1, a) + 1 + \text{rot}(n-1, b) \pmod{4}.

Similarly for Fb(n)=Fa(n1)  L  Fb(n1)F_b^{(n)} = F_a^{(n-1)} \; L \; F_b^{(n-1)}: rot(n,b)=rot(n1,a)1+rot(n1,b)(mod4)\text{rot}(n, b) = \text{rot}(n-1, a) - 1 + \text{rot}(n-1, b) \pmod{4}.

Correctness

Theorem. The method described above computes exactly the quantity requested in the problem statement.

Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer. \square

Complexity

  • Time: O(n)O(n) for precomputation, O(n)O(n) for the recursive walk. Total O(n)O(n) where n=50n = 50.
  • Space: O(n)O(n).

Answer

139776,963904\boxed{139776,963904}

Code

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

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

/*
 * Problem 220: Heighway Dragon
 *
 * L-system: Fa -> Fa R Fb, Fb -> Fa L Fb
 * Start at origin facing up (north). Find position after 10^12 steps
 * at expansion level 50.
 *
 * Approach: Recursive decomposition with precomputed displacement tables.
 *
 * Answer: 139776,963904
 */

const int MAXN = 51;

// Directions: 0=N(+y), 1=E(+x), 2=S(-y), 3=W(-x)
int dx_dir[4] = {0, 1, 0, -1};
int dy_dir[4] = {1, 0, -1, 0};

// For each level n, symbol s (0=a, 1=b), direction d:
// disp_x[n][s][d], disp_y[n][s][d] = net displacement
// rot[n][s] = net rotation (mod 4)

long long disp_x[MAXN][2][4];
long long disp_y[MAXN][2][4];
int rot[MAXN][2];

void precompute() {
    // Base case: level 0
    for (int s = 0; s < 2; s++) {
        rot[0][s] = 0;
        for (int d = 0; d < 4; d++) {
            disp_x[0][s][d] = dx_dir[d];
            disp_y[0][s][d] = dy_dir[d];
        }
    }

    // Recursive case
    for (int n = 1; n < MAXN; n++) {
        // Fa^(n) = Fa^(n-1) R Fb^(n-1)
        // rot(n, a) = rot(n-1, a) + 1 + rot(n-1, b)  (mod 4)
        rot[n][0] = (rot[n-1][0] + 1 + rot[n-1][1]) % 4;

        // Fb^(n) = Fa^(n-1) L Fb^(n-1)
        // rot(n, b) = rot(n-1, a) - 1 + rot(n-1, b)  (mod 4)
        rot[n][1] = (rot[n-1][0] - 1 + rot[n-1][1] + 4) % 4;

        for (int d = 0; d < 4; d++) {
            // Fa^(n) starting in direction d:
            // First half: Fa^(n-1) in direction d
            long long x1 = disp_x[n-1][0][d];
            long long y1 = disp_y[n-1][0][d];
            int d_after_first_a = (d + rot[n-1][0]) % 4;
            // Turn right
            int d_second_a = (d_after_first_a + 1) % 4;
            // Second half: Fb^(n-1) in direction d_second_a
            long long x2 = disp_x[n-1][1][d_second_a];
            long long y2 = disp_y[n-1][1][d_second_a];
            disp_x[n][0][d] = x1 + x2;
            disp_y[n][0][d] = y1 + y2;

            // Fb^(n) starting in direction d:
            // First half: Fa^(n-1) in direction d
            x1 = disp_x[n-1][0][d];
            y1 = disp_y[n-1][0][d];
            int d_after_first_b = (d + rot[n-1][0]) % 4;
            // Turn left
            int d_second_b = (d_after_first_b + 3) % 4; // +3 = -1 mod 4
            // Second half: Fb^(n-1) in direction d_second_b
            x2 = disp_x[n-1][1][d_second_b];
            y2 = disp_y[n-1][1][d_second_b];
            disp_x[n][1][d] = x1 + x2;
            disp_y[n][1][d] = y1 + y2;
        }
    }
}

// Find position after k steps of symbol s at level n, starting at direction d
// Returns (x, y)
pair<long long, long long> query(int n, int s, int d, long long k) {
    if (k == 0) return {0, 0};

    if (n == 0) {
        // Single step
        return {dx_dir[d], dy_dir[d]};
    }

    long long half = 1LL << (n - 1);

    // First half is always Fa^(n-1)
    // Turn depends on symbol: R for a, L for b
    // Second half is always Fb^(n-1)

    if (k <= half) {
        // Only in first half (Fa^(n-1))
        return query(n - 1, 0, d, k);
    } else {
        // Full first half + turn + partial second half
        long long x1 = disp_x[n-1][0][d];
        long long y1 = disp_y[n-1][0][d];
        int d_after = (d + rot[n-1][0]) % 4;
        int turn = (s == 0) ? 1 : 3; // R=+1 for a, L=-1=+3 for b
        int d_second = (d_after + turn) % 4;

        auto [x2, y2] = query(n - 1, 1, d_second, k - half);
        return {x1 + x2, y1 + y2};
    }
}

int main() {
    precompute();

    long long steps = 1000000000000LL; // 10^12
    int level = 50;

    // Start at origin, facing north (direction 0), symbol Fa at level 50
    auto [x, y] = query(level, 0, 0, steps);

    cout << x << "," << y << endl;
    return 0;
}