Problem 72 — Counting Fractions

solved Wed, 16 Jul 2025, 22:07 · level 3 · difficulty 8% projecteuler.net github

Consider the fraction, $\dfrac n d$, where $n$ and $d$ are positive integers. If $n \lt d$ and $\operatorname{HCF}(n,d)=1$, it is called a reduced proper fraction.

If we list the set of reduced proper fractions for $d \le 8$ in ascending order of size, we get: $$\frac 1 8, \frac 1 7, \frac 1 6, \frac 1 5, \frac 1 4, \frac 2 7, \frac 1 3, \frac 3 8, \frac 2 5, \frac 3 7, \frac 1 2, \frac 4 7, \frac 3 5, \frac 5 8, \frac 2 3, \frac 5 7, \frac 3 4, \frac 4 5, \frac 5 6, \frac 6 7, \frac 7 8$$

It can be seen that there are $21$ elements in this set.

How many elements would be contained in the set of reduced proper fractions for $d \le 1\,000\,000$?


Problem Analysis

The problem asks me to count reduced proper fractions n/d with 1 <= n < d whose numerator and denominator share no common factor other than 1, for every denominator up to one million. Two integers whose greatest common divisor is 1 are called coprime. The decisive observation is that for a fixed denominator d, the number of valid numerators is exactly the count of integers in 1..d-1 that are coprime to d - and that quantity is precisely Euler's totient function, written phi(d). So the answer to a question about coprime fractions collapses into a single sum: the running total of phi(d) from d = 2 up to the limit. I start at 2 rather than 1 because phi(1) = 1 would count 1/1, which is not a proper fraction with n < d.

The remaining work is computing phi(d) for every d efficiently. The totient is a multiplicative function given by phi(d) = d * product over distinct primes p dividing d of (1 - 1/p). Because this is a product over prime factors, I can compute all totients up to N simultaneously with a sieve modelled on the Sieve of Eratosthenes: seed each phi[d] = d, then for every prime p apply the factor (p-1)/p to each multiple of p. The cost is dominated by the inner loops, which run N/2 + N/3 + N/5 + ... times over the primes - a sum that grows like N log log N, the same near-linear bound as the Sieve of Eratosthenes itself. Memory is O(N). Both are entirely practical even at N = 10^7.

Solution Approach

Euler Totient Sieve (Python and C)

Both files implement the same totient sieve; the C version exists purely for speed. The algorithm proceeds as follows:

  • Allocate an array euler_totients of length max_d + 1 and set euler_totients[d] = d. This is the "no prime factors applied yet" identity value, matching the leading d in the product formula. In Python I write this compactly as list(range(max_d + 1)); in C it is a single initialisation loop.
  • Iterate n from 2 upward. If euler_totients[n] == n, no smaller prime has yet touched this cell, so n must itself be prime. This doubles as the primality test - I need no separate boolean sieve.
  • When n is prime, I walk every multiple j = n, 2n, 3n, ... and apply euler_totients[j] = euler_totients[j] / n * (n - 1). Each distinct prime factor of j is applied exactly once this way, so once all primes <= j have been processed, the cell at j holds its true totient.
  • I accumulate result += euler_totients[n] in the same outer loop. This is correct because every prime factor of n is at most n, so the totient of n is fully finalised by the time the outer loop reaches index n.

The two implementations differ only in their host language. The C version uses malloc for the heap array and long long to hold the running total (about 3.0e11 at max_d = 1000000, and roughly 3.0e13 at 10^7 - both comfortably inside a 64-bit integer). The Python version relies on arbitrary-precision integers and a built-in list, so there is no overflow concern at all. Both run one linear outer pass with nested per-prime inner passes.

workbench

Test cases

categoryinputanswer
dev max_d = 8 21
dev max_d = 10 31
dev max_d = 100 3043
dev max_d = 1000 304191
dev max_d = 10000 30397485
dev max_d = 100000 3039650753
main max_d = 1000000 303963552391
extra max_d = 10000000 30396356427241

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0072_s0 dev 21 correct 0.000002 (45) 0.000002 (45)
p0072_s0 dev 31 correct 0.000001 (45) 0.000002 (45)
p0072_s0 dev 3043 correct 0.000001 (45) 0.000014 (45)
p0072_s0 dev 304191 correct 0.000008 (45) 0.000140 (45)
p0072_s0 dev 30397485 correct 0.000083 (45) 0.001661 (45)
p0072_s0 dev 3039650753 correct 0.000872 (45) 0.022394 (45)
p0072_s0 main 303963552391 correct 0.009204 (45) 0.466554 (45)
p0072_s0 extra 30396356427241 correct 0.273413 (9) 6.649079 (9)

Files

Tags & blogs

Blogs