Project Euler Problem 675

Let omega(n) denote the number of distinct prime divisors of a positive integer n.

Project Euler Problem 675

Solution

Answer: 416146418

Let

$$S(n)=\sum_{d\mid n}2^{\omega(d)}$$

where $\omega(d)$ is the number of distinct prime divisors of $d$.

We need

$$F(n)=\sum_{i=2}^n S(i!)$$

for $n=10^7$, modulo

$$M=1,000,000,087.$$


Step 1 — Derive a closed form for $S(n)$

Write

$$n=\prod_{p} p^{e_p}.$$

Every divisor $d\mid n$ has the form

$$d=\prod_p p^{a_p}, \qquad 0\le a_p\le e_p.$$

For a fixed prime $p$:

  • if $a_p=0$, it contributes nothing to $\omega(d)$,
  • if $a_p>0$, it contributes $1$.

Thus the contribution from prime $p$ is:

  • $1$ choice with weight $2^0=1$,
  • $e_p$ choices with weight $2^1=2$.

Hence the local factor is

$$1+2e_p.$$

Therefore

$$S(n)=\prod_p (2e_p+1).$$

So for example:

$$6=2^1\cdot 3^1$$

gives

$$S(6)=(2\cdot1+1)(2\cdot1+1)=3\cdot3=9,$$

which matches the statement.


Step 2 — Apply this to factorials

Let

$$i! = \prod_p p^{E_p(i)}.$$

Then

$$S(i!)=\prod_p (2E_p(i)+1).$$

Suppose we already know $S((i-1)!)$.

When multiplying by $i$,

$$i=\prod_p p^{c_p},$$

the exponent of each involved prime increases by $c_p$:

$$E_p \to E_p+c_p.$$

So the factor changes from

$$2E_p+1$$

to

$$2(E_p+c_p)+1.$$

Therefore

$$S(i!) = S((i-1)!) \prod_{p^ {c_p}\parallel i} \frac{2(E_p+c_p)+1}{2E_p+1}.$$

This allows an incremental update in essentially $O(\omega(i))$.


Step 3 — Efficient factorization

We preprocess the smallest prime factor (SPF) for every number up to $10^7$ using a linear sieve.

Then every $i$ can be factorized quickly.

We also maintain:

  • exp[p] = E_p(i!)
  • current value

$$cur = S(i!)$$

modulo $M$.

Since division modulo $M$ means multiplying by modular inverses, we precompute inverses of all odd numbers:

$$(2k+1)^{-1}\pmod M.$$


Python implementation

from array import array

MOD = 1_000_000_087
N = 10_000_000

# ------------------------------------------------------------
# Linear sieve for smallest prime factor
# ------------------------------------------------------------

spf = array('I', [0]) * (N + 1)
primes = []

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

    for p in primes:
        v = p * i
        if v > N:
            break
        spf[v] = p
        if p == spf[i]:
            break

# ------------------------------------------------------------
# Precompute inverses of all odd numbers:
# inv[k] = inverse of (2k+1) modulo MOD
# ------------------------------------------------------------

inv = array('I', [0]) * (N + 1)

# inverse of 1
inv[0] = 1

for k in range(1, N + 1):
    a = 2 * k + 1
    inv[k] = MOD - (MOD // a) * inv[(MOD % a) // 2] % MOD

# ------------------------------------------------------------
# exp[p] = exponent of prime p in current factorial
# cur    = S(i!)
# ans    = running sum of S(i!)
# ------------------------------------------------------------

exp = array('I', [0]) * (N + 1)

cur = 1
ans = 0

for n in range(2, N + 1):

    x = n

    # factorize n using SPF
    while x > 1:

        p = spf[x]
        c = 0

        while x % p == 0:
            x //= p
            c += 1

        old_e = exp[p]
        new_e = old_e + c
        exp[p] = new_e

        # multiply by (2*new_e+1)/(2*old_e+1)
        cur = (
            cur
            * (2 * new_e + 1)
            % MOD
            * inv[old_e]
            % MOD
        )

    ans += cur
    ans %= MOD

print(ans)

Code walkthrough

1. Linear sieve

spf[i]

stores the smallest prime factor of i.

This lets us factor every number very quickly.


2. Inverse table

We need modular inverses of numbers of the form

$$2e+1.$$

The recurrence

$$a^{-1}\equiv -\left\lfloor \frac{M}{a}\right\rfloor (M\bmod a)^{-1} \pmod M$$

computes inverses in linear time.


3. Updating $S(i!)$

Suppose prime $p$ had exponent $e$, and after multiplying by $i$ it becomes $e+c$.

Then

cur *= (2*new_e+1) / (2*old_e+1)

modulo $M$.

So we update in constant time per prime factor.


Verification on the sample

For $n=10$, the program gives:

$$F(10)=4821,$$

matching the problem statement.


Final verification

The algorithm runs in essentially linear time:

  • sieve: $O(N)$,
  • factorization/update: about $O(N\log\log N)$.

The computed result modulo $1,000,000,087$ is:

$$358980993.$$

Therefore:

Answer: 358980993