Problem 75 — Singular Integer Right Triangles

solved Thu, 17 Jul 2025, 02:13 · level 3 · difficulty 10% projecteuler.net github

It turns out that $\pu{12 cm}$ is the smallest length of wire that can be bent to form an integer sided right angle triangle in exactly one way, but there are many more examples.

  • $\pu{\mathbf{12} \mathbf{cm}}$: $(3,4,5)$
  • $\pu{\mathbf{24} \mathbf{cm}}$: $(6,8,10)$
  • $\pu{\mathbf{30} \mathbf{cm}}$: $(5,12,13)$
  • $\pu{\mathbf{36} \mathbf{cm}}$: $(9,12,15)$
  • $\pu{\mathbf{40} \mathbf{cm}}$: $(8,15,17)$
  • $\pu{\mathbf{48} \mathbf{cm}}$: $(12,16,20)$

In contrast, some lengths of wire, like $\pu{20 cm}$, cannot be bent to form an integer sided right angle triangle, and other lengths allow more than one solution to be found; for example, using $\pu{120 cm}$ it is possible to form exactly three different integer sided right angle triangles.

  • $\pu{\mathbf{120} \mathbf{cm}}$: $(30,40,50)$, $(20,48,52)$, $(24,45,51)$

Given that $L$ is the length of the wire, for how many values of $L \le 1\,500\,000$ can exactly one integer sided right angle triangle be formed?


Problem Analysis

The structure that makes this problem tractable is the arithmetic of Pythagorean triples - integer triples (a, b, c) with a^2 + b^2 = c^2, which describe the side lengths of right-angled triangles. A triple is primitive when gcd(a, b, c) = 1, meaning the sides share no common factor; every other triple is an integer multiple of a primitive one. Euclid's formula states that every primitive triple is generated uniquely by a pair (m, n) with m > n > 0, gcd(m, n) = 1, and m, n of opposite parity (one odd, one even), via a = m^2 - n^2, b = 2mn, c = m^2 + n^2. This is the key that converts a hopeless two-dimensional search over (a, b, c) - which would be O(L^2) - into a one-dimensional enumeration over generator pairs.

What I actually care about is the perimeter L = a + b + c. For a primitive triple this collapses beautifully: L = (m^2 - n^2) + 2mn + (m^2 + n^2) = 2m^2 + 2mn = 2m(m + n). The k-th multiple of that primitive triple has perimeter exactly k * 2m(m + n). Because the smallest perimeter for a fixed m occurs at n = 1, giving 2m(m + 1) > 2m^2, the generator m is bounded by sqrt(L / 2) - a tight, analytically derived limit. The algorithm is then a sieve: for each primitive perimeter p, mark every multiple k * p <= L, counting how many triples land on each perimeter, then tally the perimeters hit exactly once. Summing L/p over all primitive perimeters follows the harmonic series, placing the total work at roughly O(L log L) - the same class as a prime sieve.

Solution Approach

Sieve over primitive perimeters (C and Python)

Both files implement the same algorithm; they differ only in language and the data structure used to accumulate counts. I enumerate generator pairs (m, n) with m > n, gcd(m, n) = 1, and opposite parity. For each valid pair I compute the primitive perimeter p = 2 * m * (m + n), then walk k = 1, 2, 3, ... incrementing a counter for k * p as long as it stays within the limit. After enumeration I count how many perimeter values were hit exactly once - that is the answer.

The outer loop runs m from 2 up to sqrt(max_perimeter / 2). The inner loop over n begins at m % 2 + 1 and steps by 2: this guarantees m - n is odd, so m and n always have opposite parity, with no per-iteration parity test. The gcd check then removes the remaining non-primitive generators. Together these two conditions ensure each primitive triple is generated exactly once, so no perimeter is ever double-counted by accident.

The languages diverge only in storage. In C I allocate a flat int array of size max_perimeter + 1 with calloc, indexing directly by perimeter value - O(1) writes and a cache-friendly final linear scan. I guard the multiple loop against overflow by widening k * p to long long before comparing. In Python I use a generator function that yields perimeters one at a time and a dictionary to accumulate counts. The benchmark shows the consequence of these language choices: on the main case (L = 1,500,000) C runs in 0.0083 s against Python's 0.3477 s, roughly a 40x gap typical of tight integer loops.

workbench

Test cases

categoryinputanswer
dev max_perimeter = 50 6
main max_perimeter = 1500000 161667
extra max_perimeter = 10000000 1067080

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0075_s0 dev 6 correct 0.000001 (47) 0.000005 (47)
p0075_s0 main 161667 correct 0.008096 (47) 0.377954 (47)
p0075_s0 extra 1067080 correct 0.146562 (15) 4.519833 (15)

Files

Tags & blogs

Blogs