Project Euler Problem 452

Define F(m,n) as the number of n-tuples of positive integers for which the product of the elements doesn't exceed m.

Project Euler Problem 452

Solution

Answer: 345558983

Let

$$F(m,n)=#{(a_1,\dots,a_n)\in \mathbb Z_{>0}^n:\ a_1a_2\cdots a_n\le m}.$$

We are asked for

$$F(10^9,10^9)\pmod{1234567891}.$$


1. Mathematical analysis

For a fixed integer $t$, define

$$d_n(t)=#{(a_1,\dots,a_n)\in \mathbb Z_{>0}^n:\ a_1a_2\cdots a_n=t}.$$

Then clearly

$$F(m,n)=\sum_{t\le m} d_n(t).$$


Step 1: Interpret $d_n(t)$

Suppose

$$t=\prod p_i^{e_i}.$$

To form an ordered $n$-tuple whose product is $t$, we distribute the exponent $e_i$ of each prime $p_i$ among the $n$ positions.

The number of ways to distribute $e_i$ identical objects into $n$ boxes is

$$\binom{n+e_i-1}{e_i}.$$

Therefore

$$d_n(t)=\prod_i \binom{n+e_i-1}{e_i}.$$

This is the generalized divisor function.


Step 2: Remove the 1's

A much more useful viewpoint is:

  • choose exactly $k$ positions that are $>1$,
  • all remaining positions are $1$.

Let $T_k(m)$ denote the number of ordered $k$-tuples of integers $>1$ whose product is $\le m$.

Then

$$F(m,n)=\sum_{k\ge0}\binom{n}{k}T_k(m).$$

Why?

  • choose the $k$ non-1 positions: $\binom nk$,
  • fill them with a valid ordered $k$-tuple counted by $T_k(m)$.

Since every factor is at least $2$,

$$2^k\le m.$$

For $m=10^9$,

$$k\le \lfloor \log_2(10^9)\rfloor = 29.$$

So only 30 terms are needed.


Step 3: Recurrence for $T_k(m)$

Define

$$T_0(m)=1.$$

For $k\ge1$,

$$T_k(m)=\sum_{a=2}^{m} T_{k-1}!\left(\left\lfloor \frac{m}{a}\right\rfloor\right).$$

This is because the first factor can be any $a\ge2$, and the remaining $k-1$ factors must have product at most $m/a$.


Step 4: Hyperbola optimization

The values of

$$\left\lfloor \frac{m}{a}\right\rfloor$$

change only $O(\sqrt m)$ times.

If

$$q=\left\lfloor \frac{m}{a}\right\rfloor,$$

then all $a$ in an interval $[i,j]$ give the same quotient $q$.

So we compute

$$T_k(m)=\sum_{\text{blocks }[i,j]} (j-i+1),T_{k-1}(q).$$

With memoization, this becomes extremely fast.


2. Python implementation

import math
from functools import lru_cache

MOD = 1234567891
N = 10**9
M = 10**9

@lru_cache(maxsize=None)
def T(k, x):
    """
    Number of ordered k-tuples of integers > 1
    whose product is <= x, modulo MOD.
    """

    if k == 0:
        return 1

    # minimum possible product is 2^k
    if x < (1 << k):
        return 0

    total = 0

    i = 2
    while i <= x:
        q = x // i
        j = x // q

        # all a in [i, j] give floor(x/a) = q
        total += (j - i + 1) * T(k - 1, q)
        total %= MOD

        i = j + 1

    return total

def solve():
    ans = 0

    # only k <= floor(log2(M)) matter
    for k in range(30):
        ans += math.comb(N, k) % MOD * T(k, M)
        ans %= MOD

    return ans

print(solve())

3. Code walkthrough

Memoized recursion

@lru_cache(maxsize=None)
def T(k, x):

Stores previously computed states.


Base case

if k == 0:
    return 1

There is exactly one empty tuple.


Pruning

if x < (1 << k):
    return 0

Any $k$-tuple with entries $>1$ has product at least $2^k$.


Quotient grouping

q = x // i
j = x // q

All integers in $[i,j]$ produce the same quotient.

This reduces complexity from $O(x)$ to roughly $O(\sqrt x)$.


Final summation

for k in range(30):
    ans += math.comb(N, k) % MOD * T(k, M)

Implements

$$F(M,N)=\sum_{k=0}^{29}\binom Nk T_k(M).$$


4. Verification

The same program gives:

$$F(10,10)=571,$$

matching the statement.

It also gives:

$$F(10^6,10^6)\bmod 1234567891 = 252903833,$$

again matching the problem statement.

So the method is validated.

The computed value for the target case is:

$$F(10^9,10^9)\bmod 1234567891 = 345558983.$$


Answer: 345558983