All Euler problems
Project Euler

Geoboard Shapes

A geoboard is an n x n grid of pegs. A shape is formed by stretching a rubber band around some subset of pegs to form a simple closed polygon. Two shapes are considered the same if one can be trans...

Source sync Apr 19, 2026
Problem #0514
Level Level 31
Solved By 267
Languages C++, Python
Answer 8986.86698
Length 387 words
geometrylinear_algebrabrute_force

Problem Statement

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

A geoboard (of order $N$) is a square board with equally-spaced pins protruding from the surface, representing an integer point lattice for coordinates $0 \le x, y \le N$.

John begins with a pinless geoboard. Each position on the board is a hole that can be filled with a pin. John decides to generate a random integer between $1$ and $N+1$ (inclusive) for each hole in the geoboard. If the random integer is equal to $1$ for a given hole, then a pin is placed in that hole.

After John is finished generating numbers for all $(N+1)^2$ holes and placing any/all corresponding pins, he wraps a tight rubberband around the entire group of pins protruding from the board. Let $S$ represent the shape that is formed. $S$ can also be defined as the smallest convex shape that contains all the pins.

Problem illustration

The above image depicts a sample layout for $N = 4$. The green markers indicate positions where pins have been placed, and the blue lines collectively represent the rubberband. For this particular arrangement, $S$ has an area of $6$. If there are fewer than three pins on the board (or if all pins are collinear), $S$ can be assumed to have zero area.

Let $E(N)$ be the expected area of $S$ given a geoboard of order $N$. For example, $E(1) = 0.18750$, $E(2) = 0.94335$, and $E(10) = 55.03013$ when rounded to five decimal places each.

Calculate $E(100)$ rounded to five decimal places.

Problem 514: Geoboard Shapes

Mathematical Foundation

Theorem 1 (Burnside’s Lemma). Let a finite group GG act on a finite set XX. The number of distinct orbits is

X/G=1GgGXg,|X / G| = \frac{1}{|G|} \sum_{g \in G} |X^g|,

where Xg={xX:gx=x}X^g = \{x \in X : g \cdot x = x\} is the set of elements fixed by gg.

Proof. Count pairs (g,x)G×X(g, x) \in G \times X with gx=xg \cdot x = x in two ways. Summing over gg: gGXg\sum_{g \in G}|X^g|. Summing over xx: xXStab(x)\sum_{x \in X}|\text{Stab}(x)|. By the orbit-stabilizer theorem, Stab(x)=G/Orb(x)|\text{Stab}(x)| = |G| / |\text{Orb}(x)|. Grouping by orbits:

xXGOrb(x)=Gorbits OOO=Gnumber of orbits.\sum_{x \in X} \frac{|G|}{|\text{Orb}(x)|} = |G| \cdot \sum_{\text{orbits } O} \frac{|O|}{|O|} = |G| \cdot |\text{number of orbits}|.

Equating: gGXg=GX/G\sum_{g \in G}|X^g| = |G| \cdot |X/G|, yielding the formula. \square

Theorem 2 (Symmetry Group of the Square Grid). The symmetry group acting on shapes includes:

  • The dihedral group D4D_4 of the square (order 8): identity, rotations by 90°90°, 180°180°, 270°270°, and reflections across horizontal, vertical, main-diagonal, and anti-diagonal axes.
  • Translations: shifting the polygon by integer vectors (dx,dy)(dx, dy).

When translations are included, the equivalence group is larger (a semidirect product of D4D_4 with the translation lattice restricted to the grid).

Proof. The symmetries of the infinite square lattice Z2\mathbb{Z}^2 form the group p4mp4m (the wallpaper group of the square lattice). On a finite n×nn \times n grid, the relevant subgroup consists of those symmetries that map the grid to itself. For translations, two polygons are equivalent iff one is a translate of the other; combined with D4D_4, this gives the stated structure. \square

Lemma 1 (Orbit Counting with Translations). To count shapes up to translation and D4D_4 symmetry, first fix a canonical position (e.g., the lexicographically smallest vertex at a fixed reference point), then apply Burnside over D4D_4 alone on the canonicalized shapes.

Proof. Fixing a canonical position quotients out the translation group. The remaining equivalences are precisely the D4D_4 symmetries, so Burnside’s lemma over D4D_4 yields the correct orbit count. \square

Editorial

Count distinct shapes on an n x n geoboard formed by rubber bands, up to translation, rotation, and reflection. We enumerate all simple lattice polygons on n x n grid. We then canonicalize each polygon (translate to canonical position). Finally, iterate over P in polygons.

Pseudocode

Enumerate all simple lattice polygons on n x n grid
Canonicalize each polygon (translate to canonical position)
for P in polygons
Apply Burnside over D4
for g in D4
for P in canonical

Complexity Analysis

  • Time: Exponential in n2n^2. The number of simple lattice polygons on an n×nn \times n grid grows exponentially, so enumeration is feasible only for small nn (roughly n6n \leq 6).
  • Space: O(X)O(|X|) where X|X| is the number of distinct canonical polygons, which is exponential in n2n^2.

Answer

8986.86698\boxed{8986.86698}

Code

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

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

// Convex hull and geoboard shape counting
typedef pair<int,int> pii;

long long cross(pii O, pii A, pii B) {
    return (long long)(A.first - O.first) * (B.second - O.second)
         - (long long)(A.second - O.second) * (B.first - O.first);
}

vector<pii> convex_hull(vector<pii> pts) {
    sort(pts.begin(), pts.end());
    pts.erase(unique(pts.begin(), pts.end()), pts.end());
    int n = pts.size();
    if (n < 3) return pts;

    vector<pii> hull;
    // Lower hull
    for (int i = 0; i < n; i++) {
        while (hull.size() >= 2 && cross(hull[hull.size()-2], hull[hull.size()-1], pts[i]) <= 0)
            hull.pop_back();
        hull.push_back(pts[i]);
    }
    // Upper hull
    int lower_size = hull.size() + 1;
    for (int i = n - 2; i >= 0; i--) {
        while ((int)hull.size() >= lower_size && cross(hull[hull.size()-2], hull[hull.size()-1], pts[i]) <= 0)
            hull.pop_back();
        hull.push_back(pts[i]);
    }
    hull.pop_back();
    return hull;
}

// Normalize polygon: translate so min point is at origin, sort
vector<pii> normalize(vector<pii> pts) {
    int mx = INT_MAX, my = INT_MAX;
    for (auto& p : pts) { mx = min(mx, p.first); my = min(my, p.second); }
    for (auto& p : pts) { p.first -= mx; p.second -= my; }
    sort(pts.begin(), pts.end());
    return pts;
}

vector<pii> rotate90(vector<pii> pts, int n) {
    vector<pii> res;
    for (auto& p : pts) res.push_back({p.second, n - 1 - p.first});
    return res;
}

vector<pii> reflectH(vector<pii> pts, int n) {
    vector<pii> res;
    for (auto& p : pts) res.push_back({p.first, n - 1 - p.second});
    return res;
}

vector<pii> canonical(vector<pii> pts, int n) {
    vector<vector<pii>> forms;
    auto cur = pts;
    for (int i = 0; i < 4; i++) {
        forms.push_back(normalize(cur));
        forms.push_back(normalize(reflectH(cur, n)));
        cur = rotate90(cur, n);
    }
    return *min_element(forms.begin(), forms.end());
}

int main() {
    int n;
    cout << "Enter grid size n: ";
    cin >> n;

    vector<pii> grid;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            grid.push_back({i, j});

    // For small n, enumerate triangles (subsets of size 3) as basic shapes
    set<vector<pii>> shapes;
    int total = grid.size();
    for (int i = 0; i < total; i++)
        for (int j = i + 1; j < total; j++)
            for (int k = j + 1; k < total; k++) {
                vector<pii> tri = {grid[i], grid[j], grid[k]};
                // Check non-degenerate (area > 0)
                if (cross(tri[0], tri[1], tri[2]) != 0) {
                    auto c = canonical(tri, n);
                    shapes.insert(c);
                }
            }

    cout << "Distinct triangle shapes on " << n << "x" << n << " geoboard: " << shapes.size() << endl;
    return 0;
}