Problem 600: Integer Sided Equiangular Hexagons

View on Project Euler

Project Euler Problem 600 Solution

EulerSolve provides an optimized solution for Project Euler Problem 600, Integer Sided Equiangular Hexagons, with C++, Python, Java, and a step-by-step mathematical explanation.

Problem Summary Let \(H(n)\) denote the number of convex equiangular hexagons with integer side lengths and perimeter at most \(n\), where two hexagons are identified when they are congruent. The checkpoints \(H(6)=1\), \(H(12)=10\), and \(H(100)=31248\) show that the count grows quickly, so direct enumeration of all side sextuples is not practical. The key observation is that an equiangular hexagon is determined by its six side lengths once the six edge directions are fixed. That turns the geometric problem into a counting problem on positive integer 6-tuples, followed by a quotient by the symmetries of a hexagon. Mathematical Approach Write the side lengths in cyclic order as \((a,b,c,d,e,f)\). Because each exterior turn is \(60^\circ\), the edge directions are \(0^\circ,60^\circ,120^\circ,180^\circ,240^\circ,300^\circ\). Summing the six edge vectors and forcing the polygon to close produces the arithmetic structure used by the solution. Step 1: Convert Geometric Closure into Two Linear Equations The \(y\)-components give $$b+c=e+f.$$ The \(x\)-components give $$2a+b-c-2d-e+f=0.$$ Eliminating \(b-e\) between these two relations yields the cleaner pair $$a-d=c-f,\qquad b-e=f-c.$$ Every positive integer sextuple satisfying these two equations corresponds to an equiangular hexagon with those side lengths, and every such hexagon arises this way....

Detailed mathematical approach

Problem Summary

Let \(H(n)\) denote the number of convex equiangular hexagons with integer side lengths and perimeter at most \(n\), where two hexagons are identified when they are congruent. The checkpoints \(H(6)=1\), \(H(12)=10\), and \(H(100)=31248\) show that the count grows quickly, so direct enumeration of all side sextuples is not practical.

The key observation is that an equiangular hexagon is determined by its six side lengths once the six edge directions are fixed. That turns the geometric problem into a counting problem on positive integer 6-tuples, followed by a quotient by the symmetries of a hexagon.

Mathematical Approach

Write the side lengths in cyclic order as \((a,b,c,d,e,f)\). Because each exterior turn is \(60^\circ\), the edge directions are \(0^\circ,60^\circ,120^\circ,180^\circ,240^\circ,300^\circ\). Summing the six edge vectors and forcing the polygon to close produces the arithmetic structure used by the solution.

Step 1: Convert Geometric Closure into Two Linear Equations

The \(y\)-components give

$$b+c=e+f.$$

The \(x\)-components give

$$2a+b-c-2d-e+f=0.$$

Eliminating \(b-e\) between these two relations yields the cleaner pair

$$a-d=c-f,\qquad b-e=f-c.$$

Every positive integer sextuple satisfying these two equations corresponds to an equiangular hexagon with those side lengths, and every such hexagon arises this way. So the problem becomes: count positive integer solutions with

$$a+b+c+d+e+f\le n,$$

then divide by congruence.

Step 2: Use Burnside's Lemma for Congruence Classes

Before quotienting by congruence, we count ordered sextuples. Congruence classes are orbits under the dihedral group \(D_6\), which has 12 symmetries: 6 rotations and 6 reflections. Burnside's lemma says

$$H(n)=\frac{1}{12}\sum_{g\in D_6}\operatorname{Fix}(g),$$

where \(\operatorname{Fix}(g)\) is the number of admissible sextuples left unchanged by the symmetry \(g\).

The 12 symmetries split into conjugacy classes, so only a few distinct counts are needed:

$$H(n)=\frac{I(n)+2R_1(n)+2R_2(n)+R_3(n)+3F_{\mathrm{even}}(n)+3F_{\mathrm{odd}}(n)}{12}.$$

Here \(I\) is the identity count, \(R_1,R_2,R_3\) come from rotations by \(60^\circ,120^\circ,180^\circ\), and \(F_{\mathrm{even}},F_{\mathrm{odd}}\) are the two reflection types.

Step 3: Rotation Counts Collapse to Compositions

A rotation by \(60^\circ\) or \(300^\circ\) forces all six sides to be equal, so the fixed tuples are

$$ (x,x,x,x,x,x),\qquad 6x\le n.$$

Hence

$$R_1(n)=\left\lfloor\frac{n}{6}\right\rfloor.$$

A rotation by \(120^\circ\) or \(240^\circ\) forces the pattern

$$ (x,y,x,y,x,y),\qquad 3x+3y\le n.$$

The number of positive pairs with \(x+y\le \lfloor n/3\rfloor\) is

$$R_2(n)=\binom{\left\lfloor n/3\right\rfloor}{2}.$$

A rotation by \(180^\circ\) forces

$$ (x,y,z,x,y,z),\qquad 2(x+y+z)\le n,$$

so

$$R_3(n)=\binom{\left\lfloor n/2\right\rfloor}{3}.$$

These are ordinary positive-composition counts.

Step 4: Parameterize the Identity Contribution

For the identity symmetry we must count every ordered sextuple satisfying the closure equations. Introduce the difference parameter

$$\delta=a-d=c-f.$$

Then we may write

$$d=\alpha,\qquad f=\beta,\qquad e=\gamma,$$

$$a=\alpha+\delta,\qquad c=\beta+\delta,\qquad b=\gamma-\delta.$$

This automatically enforces both closure equations. The perimeter becomes

$$a+b+c+d+e+f=2(\alpha+\beta+\gamma)+\delta\le n.$$

Positivity translates into

$$\alpha\ge \max(1,1-\delta),\qquad \beta\ge \max(1,1-\delta),\qquad \gamma\ge \max(1,1+\delta).$$

Let

$$T_\delta=\left\lfloor\frac{n-\delta}{2}\right\rfloor,$$

$$\alpha_0=\beta_0=\max(1,1-\delta),\qquad \gamma_0=\max(1,1+\delta).$$

After setting \(\alpha=\alpha_0+u\), \(\beta=\beta_0+v\), \(\gamma=\gamma_0+w\) with \(u,v,w\ge 0\), we get

$$u+v+w\le R_\delta:=T_\delta-(\alpha_0+\beta_0+\gamma_0).$$

The number of nonnegative triples with sum at most \(R_\delta\) is

$$\binom{R_\delta+3}{3}.$$

Therefore

$$I(n)=\sum_{-n\le \delta\le n,\ R_\delta\ge 0}\binom{R_\delta+3}{3}.$$

This is exactly the linear summation used by the implementations.

Step 5: Count the First Reflection Type

One reflection class fixes sextuples of the form

$$ (u,v,w,u+v-w,w,v).$$

Substituting this pattern into the closure equations shows that it is the general fixed form. Positivity requires

$$u+v-w\ge 1\iff w\le u+v-1,$$

and the perimeter condition is

$$2u+3v+w\le n.$$

So for each positive pair \((u,v)\), the third parameter \(w\) can range from \(1\) up to

$$\min(u+v-1,\ n-2u-3v).$$

The implementations do not iterate over \(w\). Instead, for each fixed \(v\), they split the \(u\)-range at the point where the minimum switches from \(u+v-1\) to \(n-2u-3v\). That turns the entire reflection class into a single linear-time arithmetic summation.

Step 6: Count the Second Reflection Type and Assemble Burnside

The other reflection class fixes sextuples of the simpler form

$$ (u,u,v,u,u,v).$$

Here the closure equations are automatic, and the perimeter condition is

$$4u+2v\le n.$$

If we set \(M=\lfloor n/2\rfloor\), this becomes

$$2u+v\le M.$$

For each \(u\ge 1\), the number of admissible positive \(v\) is \(M-2u\). Hence

$$F_{\mathrm{odd}}(n)=\sum_{u=1}^{\lfloor (M-1)/2\rfloor}(M-2u).$$

Combining both reflection counts with the rotation counts and the identity count gives the final formula

$$\boxed{H(n)=\frac{I(n)+2R_1(n)+2R_2(n)+R_3(n)+3F_{\mathrm{even}}(n)+3F_{\mathrm{odd}}(n)}{12}.}$$

Worked Example: Computing \(H(12)\)

This example matches one of the published checkpoints. First, the rotation terms are

$$R_1(12)=\left\lfloor\frac{12}{6}\right\rfloor=2,\qquad R_2(12)=\binom{4}{2}=6,\qquad R_3(12)=\binom{6}{3}=20.$$

For the identity term, only \(\delta=-2,-1,0,1,2\) contribute. Their values are

$$1,\ 4,\ 20,\ 4,\ 1,$$

so

$$I(12)=30.$$

For the two reflection classes, the corresponding sums give

$$F_{\mathrm{even}}(12)=12,\qquad F_{\mathrm{odd}}(12)=6.$$

Burnside then yields

$$H(12)=\frac{30+2\cdot 2+2\cdot 6+20+3\cdot 12+3\cdot 6}{12}=\frac{120}{12}=10,$$

which matches the stated checkpoint exactly.

How the Code Works

The C++, Python, and Java implementations follow the Burnside decomposition directly. They first evaluate the three rotation contributions from their closed forms, because those require only a few integer divisions and binomial formulas.

Next, the implementation computes the identity contribution by looping over every feasible integer value of the difference parameter \(\delta\). For each \(\delta\), it derives the minimal positive starting values forced by positivity, converts the remaining freedom into a nonnegative triple \((u,v,w)\), and adds the stars-and-bars count \(\binom{R_\delta+3}{3}\) whenever \(R_\delta\ge 0\).

For the first reflection class, the implementation loops over one side parameter and uses a split point to sum the second parameter range in two arithmetic pieces, avoiding an expensive inner enumeration. For the second reflection class, it uses the direct one-dimensional formula coming from \(2u+v\le \lfloor n/2\rfloor\).

Finally, it combines the six class counts with the Burnside weights \(1,2,2,1,3,3\) and divides by 12. The numeric types differ by language, but all three versions use exact integer arithmetic rather than floating point.

Complexity Analysis

The rotation terms and the second reflection class are \(O(1)\). The identity term is a single loop over \(2n+1\) possible values of \(\delta\), so it is \(O(n)\). The first reflection class is also evaluated by a single linear summation over the relevant side parameter range, again \(O(n)\). Therefore the overall running time is \(O(n)\).

The algorithm stores only a fixed number of counters and temporary arithmetic values, so the memory usage is \(O(1)\).

Footnotes and References

  1. Project Euler problem page: https://projecteuler.net/problem=600
  2. Burnside's lemma: Wikipedia — Burnside's lemma
  3. Dihedral group: Wikipedia — Dihedral group
  4. Stars and bars: Wikipedia — Stars and bars
  5. Equiangular polygon: Wikipedia — Equiangular polygon

Problem 600 source code

C++

#include <cstdint>
#include <algorithm>
#include <iostream>

// Project Euler 600: Integer Sided Equiangular Hexagons
//
// An equiangular hexagon has all interior angles 120 degrees, so successive edge directions
// differ by 60 degrees. With side lengths (a,b,c,d,e,f) around the hexagon, the closure
// condition becomes:
//   a - d = c - f,
//   b - e = f - c.
//
// We count ordered side-length 6-tuples of positive integers satisfying closure and
// perimeter <= n, then quotient by the dihedral symmetries of a hexagon (rotations and
// reflections) using Burnside's lemma.
//
// Burnside requires the number of tuples fixed by each symmetry. The rotation cases reduce
// to simple compositions. Reflections split into two types and reduce to tractable integer
// sums. The identity count can be computed by summing over the difference parameter p:
// writing d=A, f=C, e=E and letting
//   a = A + p,  b = E - p,  c = C + p,
// yields perimeter 2(A+C+E)+p and positivity bounds that depend only on p.

using u64 = std::uint64_t;
using u128 = unsigned __int128;
using i64 = std::int64_t;

static void print_u128(u128 x) {
    if (x == 0) {
        std::cout << '0';
        return;
    }
    char buf[64];
    int n = 0;
    while (x > 0) {
        buf[n++] = (char)('0' + (u64)(x % 10));
        x /= 10;
    }
    while (n--) std::cout << buf[n];
}

static inline u128 choose2(u64 t) {
    if (t < 2) return 0;
    return (u128)t * (u128)(t - 1) / 2;
}

static inline u128 choose3(u64 t) {
    if (t < 3) return 0;
    return (u128)t * (u128)(t - 1) * (u128)(t - 2) / 6;
}

static inline u128 tet(u64 r) {
    // Number of nonnegative integer triples with sum <= r is C(r+3,3).
    return (u128)(r + 1) * (u128)(r + 2) * (u128)(r + 3) / 6;
}

static u128 fix_identity(u64 n) {
    // Sum over p (called x in many derivations).
    // For fixed p, let A=d, C=f, E=e. Then:
    //   a=A+p >=1  => A >= 1-p
    //   c=C+p >=1  => C >= 1-p
    //   b=E-p >=1  => E >= p+1
    // Perimeter: 2(A+C+E)+p <= n  => A+C+E <= floor((n-p)/2).

    u128 total = 0;
    for (i64 p = -(i64)n; p <= (i64)n; ++p) {
        const i64 T = ((i64)n - p) / 2;
        const i64 A0 = std::max<i64>(1, 1 - p);
        const i64 E0 = std::max<i64>(1, p + 1);
        const i64 base = 2 * A0 + E0;
        if (T < base) continue;
        const i64 R = T - base;
        total += tet((u64)R);
    }
    return total;
}

static u128 fix_reflection_even(u64 n) {
    // Representative reflection constraints: (a,b,c,d,e,f) = (a,b,c,d,c,b).
    // Closure gives d = a - c + b.
    // Perimeter: 2a + 3b + c <= n, and d>=1 implies c <= a+b-1.

    u128 total = 0;
    if (n < 6) return 0;

    const u64 bmax = (n - 3) / 3; // need 3b+3 <= n for any solution
    for (u64 b = 1; b <= bmax; ++b) {
        const i64 M = (i64)n - 3 * (i64)b; // M = n - 3b

        // Need M - 2a >= 1 for c>=1.
        const i64 Amax = (M - 1) / 2;
        if (Amax <= 0) continue;

        // Split where a+b-1 <= M-2a  <=>  3a <= M-b+1.
        const i64 Asplit = ((i64)n - 4 * (i64)b + 1) / 3;
        i64 m = std::min<i64>(Amax, std::max<i64>(0, Asplit));

        // Region 1: a=1..m, c_max = a+b-1.
        if (m >= 1) {
            const u128 sum_a = (u128)m * (u128)(m + 1) / 2;
            total += sum_a + (u128)(b - 1) * (u128)m;
        } else {
            m = 0;
        }

        // Region 2: a=m+1..Amax, c_max = M-2a.
        if (Amax > m) {
            const i64 cnt = Amax - m;
            const u128 sum_a = (u128)Amax * (u128)(Amax + 1) / 2 - (u128)m * (u128)(m + 1) / 2;
            total += (u128)M * (u128)cnt - (u128)2 * sum_a;
        }
    }
    return total;
}

static u128 fix_reflection_odd(u64 n) {
    // Representative odd reflection fixes: (a,b,c,d,e,f)=(a,a,c,a,a,c).
    // Condition: 4a+2c <= n  <=> 2a + c <= floor(n/2).

    const u64 T = n / 2;
    if (T < 3) return 0;
    const u64 amax = (T - 1) / 2;
    // Sum_{a=1..amax} (T - 2a)
    return (u128)amax * (u128)(T - (amax + 1));
}

static u128 H(u64 n) {
    const u128 id = fix_identity(n);
    const u128 r1 = (u64)(n / 6);
    const u64 T3 = n / 3;
    const u128 r2 = choose2(T3);
    const u64 T2 = n / 2;
    const u128 r3 = choose3(T2);
    const u128 fe = fix_reflection_even(n);
    const u128 fo = fix_reflection_odd(n);

    const u128 num = id + 2 * r1 + 2 * r2 + r3 + 3 * fe + 3 * fo;
    return num / 12;
}

int main() {
    // Validation points from the statement.
    if (H(6) != 1) {
        std::cerr << "Validation failed: H(6)\n";
        return 1;
    }
    if (H(12) != 10) {
        std::cerr << "Validation failed: H(12)\n";
        return 1;
    }
    if (H(100) != 31248) {
        std::cerr << "Validation failed: H(100)\n";
        return 1;
    }

    print_u128(H(55106));
    std::cout << "\n";
    return 0;
}

Python

def tet(r):
    if r < 0: return 0
    return (r + 1) * (r + 2) * (r + 3) // 6

def choose2(t):
    if t < 2: return 0
    return t * (t - 1) // 2

def choose3(t):
    if t < 3: return 0
    return t * (t - 1) * (t - 2) // 6

def fix_identity(n):
    total = 0
    for p in range(-n, n + 1):
        T = (n - p) // 2
        A0 = max(1, 1 - p)
        E0 = max(1, p + 1)
        base = 2 * A0 + E0
        if T < base: continue
        R = T - base
        total += tet(R)
    return total

def fix_reflection_even(n):
    total = 0
    if n < 6: return 0
    bmax = (n - 3) // 3
    for b in range(1, bmax + 1):
        M = n - 3 * b
        Amax = (M - 1) // 2
        if Amax <= 0: continue
        Asplit = (n - 4 * b + 1) // 3
        m = min(Amax, max(0, Asplit))
        
        if m >= 1:
            sum_a = m * (m + 1) // 2
            total += sum_a + (b - 1) * m
        else:
            m = 0
            
        if Amax > m:
            cnt = Amax - m
            sum_a = Amax * (Amax + 1) // 2 - m * (m + 1) // 2
            total += M * cnt - 2 * sum_a
            
    return total

def fix_reflection_odd(n):
    T = n // 2
    if T < 3: return 0
    amax = (T - 1) // 2
    return amax * (T - (amax + 1))

def H(n):
    id_val = fix_identity(n)
    r1 = n // 6
    T3 = n // 3
    r2 = choose2(T3)
    T2 = n // 2
    r3 = choose3(T2)
    fe = fix_reflection_even(n)
    fo = fix_reflection_odd(n)
    
    num = id_val + 2 * r1 + 2 * r2 + r3 + 3 * fe + 3 * fo
    return num // 12

def solve():
    return str(H(55106))

if __name__ == '__main__':
    print(solve())

Java

import java.math.BigInteger;

public class Euler600 {

    static BigInteger choose2(long t) {
        if (t < 2)
            return BigInteger.ZERO;
        BigInteger T = BigInteger.valueOf(t);
        return T.multiply(BigInteger.valueOf(t - 1)).divide(BigInteger.valueOf(2));
    }

    static BigInteger choose3(long t) {
        if (t < 3)
            return BigInteger.ZERO;
        BigInteger T = BigInteger.valueOf(t);
        return T.multiply(BigInteger.valueOf(t - 1)).multiply(BigInteger.valueOf(t - 2)).divide(BigInteger.valueOf(6));
    }

    static BigInteger tet(long r) {
        if (r < 0)
            return BigInteger.ZERO;
        BigInteger R = BigInteger.valueOf(r);
        return R.add(BigInteger.ONE).multiply(R.add(BigInteger.valueOf(2))).multiply(R.add(BigInteger.valueOf(3)))
                .divide(BigInteger.valueOf(6));
    }

    static BigInteger fixIdentity(long n) {
        BigInteger total = BigInteger.ZERO;
        for (long p = -n; p <= n; p++) {
            long T = (n - p) / 2;
            long A0 = Math.max(1, 1 - p);
            long E0 = Math.max(1, p + 1);
            long base = 2 * A0 + E0;
            if (T < base)
                continue;
            long R = T - base;
            total = total.add(tet(R));
        }
        return total;
    }

    static BigInteger fixReflectionEven(long n) {
        BigInteger total = BigInteger.ZERO;
        if (n < 6)
            return BigInteger.ZERO;

        long bmax = (n - 3) / 3;
        for (long b = 1; b <= bmax; b++) {
            long M = n - 3 * b;
            long Amax = (M - 1) / 2;
            if (Amax <= 0)
                continue;

            long Asplit = (n - 4 * b + 1) / 3;
            long m = Math.min(Amax, Math.max(0, Asplit));

            if (m >= 1) {
                long sumA = m * (m + 1) / 2;
                long toAdd = sumA + (b - 1) * m;
                total = total.add(BigInteger.valueOf(toAdd));
            } else {
                m = 0;
            }

            if (Amax > m) {
                long cnt = Amax - m;
                long sumA = Amax * (Amax + 1) / 2 - m * (m + 1) / 2;
                long toAdd = M * cnt - 2 * sumA;
                total = total.add(BigInteger.valueOf(toAdd));
            }
        }
        return total;
    }

    static BigInteger fixReflectionOdd(long n) {
        long T = n / 2;
        if (T < 3)
            return BigInteger.ZERO;
        long amax = (T - 1) / 2;
        long toAdd = amax * (T - (amax + 1));
        return BigInteger.valueOf(toAdd);
    }

    static BigInteger H(long n) {
        BigInteger idVal = fixIdentity(n);
        BigInteger r1 = BigInteger.valueOf(n / 6);
        long T3 = n / 3;
        BigInteger r2 = choose2(T3);
        long T2 = n / 2;
        BigInteger r3 = choose3(T2);
        BigInteger fe = fixReflectionEven(n);
        BigInteger fo = fixReflectionOdd(n);

        BigInteger num = idVal.add(r1.multiply(BigInteger.valueOf(2)))
                .add(r2.multiply(BigInteger.valueOf(2)))
                .add(r3)
                .add(fe.multiply(BigInteger.valueOf(3)))
                .add(fo.multiply(BigInteger.valueOf(3)));
        return num.divide(BigInteger.valueOf(12));
    }

    public static String solve() {
        return H(55106).toString();
    }

    public static void main(String[] args) {
        System.out.println(solve());
    }
}