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...
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.

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 act on a finite set . The number of distinct orbits is
where is the set of elements fixed by .
Proof. Count pairs with in two ways. Summing over : . Summing over : . By the orbit-stabilizer theorem, . Grouping by orbits:
Equating: , yielding the formula.
Theorem 2 (Symmetry Group of the Square Grid). The symmetry group acting on shapes includes:
- The dihedral group of the square (order 8): identity, rotations by , , , and reflections across horizontal, vertical, main-diagonal, and anti-diagonal axes.
- Translations: shifting the polygon by integer vectors .
When translations are included, the equivalence group is larger (a semidirect product of with the translation lattice restricted to the grid).
Proof. The symmetries of the infinite square lattice form the group (the wallpaper group of the square lattice). On a finite 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 , this gives the stated structure.
Lemma 1 (Orbit Counting with Translations). To count shapes up to translation and symmetry, first fix a canonical position (e.g., the lexicographically smallest vertex at a fixed reference point), then apply Burnside over alone on the canonicalized shapes.
Proof. Fixing a canonical position quotients out the translation group. The remaining equivalences are precisely the symmetries, so Burnside’s lemma over yields the correct orbit count.
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 . The number of simple lattice polygons on an grid grows exponentially, so enumeration is feasible only for small (roughly ).
- Space: where is the number of distinct canonical polygons, which is exponential in .
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#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;
}
"""
Problem 514: Geoboard Shapes
Count distinct shapes on an n x n geoboard formed by rubber bands,
up to translation, rotation, and reflection.
"""
from itertools import combinations
from math import gcd
def convex_hull(points):
"""Andrew's monotone chain convex hull algorithm."""
points = sorted(set(points))
if len(points) <= 1:
return points
def cross(O, A, B):
return (A[0] - O[0]) * (B[1] - O[1]) - (A[1] - O[1]) * (B[0] - O[0])
lower = []
for p in points:
while len(lower) >= 2 and cross(lower[-2], lower[-1], p) <= 0:
lower.pop()
lower.append(p)
upper = []
for p in reversed(points):
while len(upper) >= 2 and cross(upper[-2], upper[-1], p) <= 0:
upper.pop()
upper.append(p)
return lower[:-1] + upper[:-1]
def polygon_area_2(pts):
"""Twice the signed area of a polygon (shoelace formula)."""
n = len(pts)
area = 0
for i in range(n):
j = (i + 1) % n
area += pts[i][0] * pts[j][1] - pts[j][0] * pts[i][1]
return abs(area)
def normalize_polygon(pts):
"""Normalize a polygon to canonical form (translation to origin, sorted)."""
if not pts:
return tuple()
min_x = min(p[0] for p in pts)
min_y = min(p[1] for p in pts)
translated = tuple(sorted((p[0] - min_x, p[1] - min_y) for p in pts))
return translated
def rotate_90(pts, n):
"""Rotate points 90 degrees clockwise on n x n grid."""
return [(p[1], n - 1 - p[0]) for p in pts]
def reflect_h(pts, n):
"""Reflect across horizontal axis."""
return [(p[0], n - 1 - p[1]) for p in pts]
def canonical_form(pts, n):
"""Find canonical form under D4 symmetry group."""
forms = []
current = list(pts)
for _ in range(4):
forms.append(normalize_polygon(current))
ref = reflect_h(current, n)
forms.append(normalize_polygon(ref))
current = rotate_90(current, n)
return min(forms)
def count_geoboard_shapes(n):
"""Count distinct convex shapes on n x n geoboard (using convex hulls as proxy)."""
grid_points = [(i, j) for i in range(n) for j in range(n)]
shapes = set()
# Enumerate subsets of size 3..n*n that form convex polygons
# For tractability, only consider convex hulls of small subsets
max_subset = min(6, len(grid_points))
for size in range(3, max_subset + 1):
for subset in combinations(grid_points, size):
hull = convex_hull(list(subset))
if len(hull) >= 3 and polygon_area_2(hull) > 0:
# Only count if all points are on the hull (convex polygon)
if len(hull) == len(subset):
canon = canonical_form(hull, n)
shapes.add(canon)
return len(shapes)
# Compute for small grids
results = {}
for n in range(2, 6):
c = count_geoboard_shapes(n)
results[n] = c
print(f"Distinct convex shapes on {n}x{n} geoboard: {c}")