Project Euler Problem 276

Consider the triangles with integer sides a, b and c with a le b le c.

Project Euler Problem 276

Solution

Answer: 5777137137739632912

Let

$$T(N)=#{(a,b,c): a\le b\le c,\ a+b>c,\ a+b+c\le N}$$

be the number of all integer-sided triangles with perimeter at most $N$, and let

$$P(N)$$

be the number of primitive ones (i.e. $\gcd(a,b,c)=1$).

We want $P(10^7)$.


1. Mathematical analysis

Step 1: Count all integer triangles of perimeter $n$

Let $t(n)$ be the number of integer triangles with perimeter exactly $n$.

A classical result gives:

$$t(n)=\operatorname{round}!\left(\frac{n^2}{12}\right) -\left\lfloor \frac n4 \right\rfloor \left\lfloor \frac{n+2}{4}\right\rfloor.$$

We can verify on small examples:

  • $n=3$: only $(1,1,1)$, so $t(3)=1$
  • $n=5$: only $(1,2,2)$, so $t(5)=1$
  • $n=6$: $(1,2,3)$ fails triangle inequality, but $(2,2,2)$, $(1,2,3)$ invalid, $(1,1,4)$ invalid, so only $(2,2,2)$ and $(1,2,3)$ excluded ⇒ $t(6)=1$

The formula matches.

Define the cumulative count:

$$A(N)=\sum_{n\le N} t(n).$$

Since $t(n)$ is quadratic with a periodic correction (period 12), $A(N)$ becomes a cubic quasipolynomial. Writing

$$N=12q+r,\qquad 0\le r<12,$$

we obtain an exact $O(1)$ formula for $A(N)$. For example:

$$A(12q)=6q^2(2q+1),$$

and similarly for the other 11 residue classes.


Step 2: Primitive triangles via Möbius inversion

Every triangle $(a,b,c)$ has a unique gcd $d$:

$$(a,b,c)=d(a',b',c')$$

with $(a',b',c')$ primitive.

Thus:

$$A(N)=\sum_{d\le N} P!\left(\left\lfloor \frac Nd\right\rfloor\right).$$

This is a divisor convolution.

Applying Möbius inversion:

$$P(N) = \sum_{d\le N} \mu(d), A!\left(\left\lfloor \frac Nd\right\rfloor\right),$$

where $\mu$ is the Möbius function.

So the task reduces to:

  1. Compute $\mu(d)$ up to $10^7$ using a linear sieve.
  2. Compute the Mertens prefix sum

$$M(n)=\sum_{k\le n}\mu(k).$$

  1. Group equal values of $\lfloor N/d\rfloor$:

$$P(N) = \sum A(v), \bigl(M(r)-M(l-1)\bigr),$$

where $v=\lfloor N/l\rfloor$ and $r=\lfloor N/v\rfloor$.

This reduces the summation to $O(\sqrt N)$.


2. Python implementation

from array import array

N = 10_000_000

def cumulative_triangles(n):
    """
    A(n) = number of integer-sided triangles
    with perimeter <= n.
    Exact O(1) quasipolynomial formula.
    """
    q, r = divmod(n, 12)

    if r == 0:
        return 6 * q * q * (2 * q + 1)
    elif r == 1:
        return q * (12 * q * q + 9 * q + 2)
    elif r == 2:
        return 3 * q * (2 * q + 1) ** 2
    elif r == 3:
        return 12 * q**3 + 15 * q**2 + 6 * q + 1
    elif r == 4:
        return (2 * q + 1) * (6 * q**2 + 6 * q + 1)
    elif r == 5:
        return 12 * q**3 + 21 * q**2 + 12 * q + 2
    elif r == 6:
        return 3 * (q + 1) * (2 * q + 1) ** 2
    elif r == 7:
        return (q + 1) * (12 * q**2 + 15 * q + 5)
    elif r == 8:
        return 6 * (q + 1) ** 2 * (2 * q + 1)
    elif r == 9:
        return 3 * (q + 1) ** 2 * (4 * q + 3)
    elif r == 10:
        return (q + 1) * (12 * q**2 + 24 * q + 11)
    else:  # r == 11
        return 3 * (q + 1) ** 2 * (4 * q + 5)

# ----- Linear sieve for Möbius function -----

mu = array('b', [0]) * (N + 1)
lp = array('I', [0]) * (N + 1)
primes = array('I')

mu[1] = 1

for i in range(2, N + 1):
    if lp[i] == 0:
        lp[i] = i
        primes.append(i)
        mu[i] = -1

    li = lp[i]

    for p in primes:
        v = i * p
        if v > N or p > li:
            break

        lp[v] = p

        if p == li:
            mu[v] = 0
            break
        else:
            mu[v] = -mu[i]

# ----- Prefix sums of Möbius -----

M = array('i', [0]) * (N + 1)

running = 0
for i in range(1, N + 1):
    running += mu[i]
    M[i] = running

# ----- Möbius inversion with floor-division grouping -----

answer = 0
l = 1

while l <= N:
    v = N // l
    r = N // v

    answer += cumulative_triangles(v) * (M[r] - M[l - 1])

    l = r + 1

print(answer)

3. Code walkthrough

cumulative_triangles(n)

Computes

$$A(n)=\sum_{k\le n} t(k)$$

in constant time using the exact residue-class formulas modulo 12.

This avoids storing all triangle counts up to $10^7$.


Linear sieve

We compute the Möbius function:

$$\mu(n)= \begin{cases} 1 & n=1\ (-1)^k & n \text{ squarefree with }k\text{ primes}\ 0 & \text{otherwise} \end{cases}$$

using an $O(N)$ linear sieve.


Prefix Mertens function

We store:

$$M(n)=\sum_{k\le n}\mu(k)$$

to allow fast range sums:

$$\sum_{d=l}^r \mu(d)=M(r)-M(l-1).$$


Harmonic grouping trick

Since

$$\left\lfloor \frac Nd\right\rfloor$$

takes only $O(\sqrt N)$ distinct values, we process intervals where it is constant.

This reduces the final summation from $10^7$ iterations to roughly $2\sqrt N$.


4. Small verification

Brute force checks:

  • $N=10$: result = 8
  • $N=20$: result = 58
  • $N=50$: result = 789
  • $N=100$: result = 6033

The optimized method exactly matches brute force.

For $N=10^7$, the count should scale cubically (roughly proportional to $N^3$), so a value around $10^{18}$ is reasonable.

The computation yields:

$$5777137137739632912$$

Answer: 5777137137739632912