All Euler problems
Project Euler

Long Products

Define F(m, n) as the number of n -tuples (a_1, a_2,..., a_n) of positive integers for which the product does not exceed m: F(m, n) = |bigl{(a_1,..., a_n) in Z_(>0)^n: a_1 a_2... a_n <= mbigr}|. Gi...

Source sync Apr 19, 2026
Problem #0452
Level Level 19
Solved By 701
Languages C++, Python
Answer 345558983
Length 257 words
modular_arithmeticnumber_theorylinear_algebra

Problem Statement

This archive keeps the full statement, math, and original media on the page.

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

You are given that \(F(10, 10) = 571\) and \(F(10^6, 10^6) \bmod 1\,234\,567\,891 = 252903833\).

Find \(F(10^9, 10^9) \bmod 1\,234\,567\,891\).

Problem 452: Long Products

Mathematical Foundation

Theorem 1 (Reduction to Piltz divisor sum). Grouping tuples by product value,

F(m,n)=k=1mdn(k),F(m, n) = \sum_{k=1}^{m} d_n(k),

where dn(k)d_n(k) counts the number of ordered factorizations of kk into exactly nn positive integer factors (the nn-th Piltz divisor function).

Proof. Each nn-tuple (a1,,an)(a_1, \ldots, a_n) with product kk is an ordered factorization of kk into nn parts. Summing over all kmk \le m counts every valid tuple exactly once. \square

Lemma 1 (Multiplicativity of dnd_n). The function dnd_n is multiplicative: for gcd(a,b)=1\gcd(a, b) = 1, dn(ab)=dn(a)dn(b)d_n(ab) = d_n(a) d_n(b). At a prime power,

dn(pa)=(n+a1a).d_n(p^a) = \binom{n + a - 1}{a}.

Proof. Multiplicativity follows from the Chinese Remainder Theorem applied to ordered factorizations. For pap^a, distributing aa copies of prime pp among nn positions is a stars-and-bars count: (n+a1a)\binom{n + a - 1}{a}. \square

Lemma 2 (Exponent bound). Since m=109m = 10^9 and 230>1092^{30} > 10^9, every prime power pamp^a \le m satisfies a29a \le 29. Hence (n+a1a)\binom{n + a - 1}{a} is a polynomial of degree at most 2929 in nn, evaluable modulo the prime 12345678911\,234\,567\,891.

Proof. If pa109p^a \le 10^9 then alog2(109)=29a \le \lfloor \log_2(10^9) \rfloor = 29. The binomial coefficient (n+a1a)=(n+a1)(n+a2)na!\binom{n + a - 1}{a} = \frac{(n+a-1)(n+a-2)\cdots n}{a!} is a polynomial of degree aa in nn. \square

Theorem 2 (Bucket decomposition). The set {m/k:k=1,,m}\{\lfloor m/k \rfloor : k = 1, \ldots, m\} has at most 2m2\lfloor\sqrt{m}\rfloor distinct values. Storing partial sums S[v]S[v] for each such value vv suffices to compute F(m,n)F(m, n) with O(m)O(\sqrt{m}) storage.

Proof. For kmk \le \sqrt{m}, the value m/k\lfloor m/k \rfloor can be any of at most m\sqrt{m} values. For k>mk > \sqrt{m}, m/k<m\lfloor m/k \rfloor < \sqrt{m}, giving at most m\sqrt{m} values. The union has at most 2m2\sqrt{m} elements. \square

Editorial

We initialize bucket arrays. We then precompute C[a] = binomial(n+a-1, a) mod P for a = 0, …, 29. Finally, process small primes (p <= B).

Pseudocode

Input: m, n, modulus P
Output: F(m, n) mod P
Let B = floor(sqrt(m))
Initialize bucket arrays:
Precompute C[a] = binomial(n+a-1, a) mod P for a = 0, ..., 29
Process small primes (p <= B):
For each bucket value v in decreasing order
Process large primes (p > B) via segmented sieve:
For each such prime p
Return hi[1]   (which stores S[floor(m/1)] = S[m] = F(m,n))

Complexity Analysis

  • Time: O(m)O(m), dominated by the large-prime phase. The small-prime phase costs O(B2/logB)=O(m/logm)O(B^2 / \log B) = O(m / \log m). Each large prime pp updates at most m/pB\lfloor m/p \rfloor \le B buckets; summing over large primes gives O(p>Bm/p)=O(m)O(\sum_{p > B} m/p) = O(m).
  • Space: O(m)O(\sqrt{m}) for the two bucket arrays and the segmented sieve buffer.

Answer

345558983\boxed{345558983}

Code

Each problem page includes the exact C++ and Python source files from the local archive.

C++ project_euler/problem_452/solution.cpp
/*
 * Project Euler Problem 452 — Long Products
 *
 * F(m,n) = number of n-tuples (a1,...,an) with ai >= 1 and product <= m.
 * Compute F(10^9, 10^9) mod 1234567891.
 *
 * F(m,n) = sum_{k=1}^{m} d_n(k) where d_n is the n-th Piltz divisor function.
 * d_n is multiplicative: d_n(p^a) = C(n+a-1, a).
 *
 * Algorithm:
 *   - Maintain partial sums S[v] for O(sqrt(m)) bucket values v = floor(m/k).
 *   - Process small primes (p <= sqrt(m)) with full exponent handling.
 *   - Process large primes (p > sqrt(m)) via segmented sieve, a=1 only.
 *
 * Compile: g++ -O2 -o solution solution_final.cpp
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <vector>
#include <algorithm>
#include <ctime>

using namespace std;
typedef long long ll;

const ll MOD = 1234567891LL;

ll power(ll base, ll exp, ll m) {
    ll r = 1; base %= m;
    while (exp > 0) {
        if (exp & 1) r = r * base % m;
        base = base * base % m;
        exp >>= 1;
    }
    return r;
}

ll modinv(ll a, ll m) { return power(a, m - 2, m); }

ll binom_mod(ll n_val, int a) {
    if (a == 0) return 1;
    ll num = 1, den = 1;
    for (int i = 1; i <= a; i++) {
        num = num * ((n_val + a - i) % MOD) % MOD;
        den = den * ((ll)i % MOD) % MOD;
    }
    return num * modinv(den, MOD) % MOD;
}

ll solve(ll m, ll n) {
    if (m <= 0) return 0;
    if (m == 1) return 1;

    int sqm = (int)sqrt((double)m);
    while ((ll)(sqm + 1) * (sqm + 1) <= m) sqm++;
    int sz = sqm + 1;

    vector<ll> lo(sz, 0), hi(sz, 0);
    for (int i = 1; i < sz; i++) { lo[i] = 1; hi[i] = 1; }

    auto getS = [&](ll v) -> ll {
        return (v <= sqm) ? lo[(int)v] : hi[(int)(m / v)];
    };

    int max_a = (m >= 2) ? (int)(log((double)m) / log(2.0)) + 2 : 1;
    vector<ll> bc(max_a + 1);
    for (int a = 0; a <= max_a; a++) bc[a] = binom_mod(n, a);

    // Sieve small primes
    vector<int> small_primes;
    {
        vector<bool> ip(sqm + 1, true);
        ip[0] = ip[1] = false;
        for (int i = 2; (ll)i * i <= sqm; i++)
            if (ip[i])
                for (int j = i * i; j <= sqm; j += i)
                    ip[j] = false;
        for (int i = 2; i <= sqm; i++)
            if (ip[i]) small_primes.push_back(i);
    }

    printf("  sqm=%d, small primes: %zu\n", sqm, small_primes.size());

    // Step 1: Process small primes
    for (int p : small_primes) {
        // hi buckets (v > sqm), processed in decreasing v order (increasing k)
        for (int k = 1; k < sz; k++) {
            ll v = m / k;
            if (v <= sqm) break;
            if (v < p) break;
            ll pk = p; int a = 1;
            while (pk <= v) {
                hi[k] = (hi[k] + bc[a] * getS(v / pk)) % MOD;
                if (pk > v / p) break;
                pk *= p; a++;
            }
        }
        // lo buckets
        for (int v = sqm; v >= p; v--) {
            ll pk = p; int a = 1;
            while (pk <= v) {
                lo[v] = (lo[v] + bc[a] * getS(v / pk)) % MOD;
                if (pk > v / p) break;
                pk *= p; a++;
            }
        }
    }

    // Step 2: Process large primes via segmented sieve
    // For p > sqm: only a=1. Only hi buckets affected.
    // Process primes in DECREASING order within each segment.
    ll bc1 = bc[1];
    int seg_size = 1 << 19;  // 512K
    vector<bool> seg(seg_size);

    ll total_large = 0;
    for (ll seg_lo = (ll)sqm + 1; seg_lo <= m; seg_lo += seg_size) {
        ll seg_hi_val = min(seg_lo + seg_size - 1, m);
        int seg_len = (int)(seg_hi_val - seg_lo + 1);
        fill(seg.begin(), seg.begin() + seg_len, true);

        for (int p : small_primes) {
            ll start = ((seg_lo + p - 1) / p) * p;
            if (start < (ll)p * p) start = (ll)p * p;  // composites only
            for (ll j = start; j <= seg_hi_val; j += p)
                seg[(int)(j - seg_lo)] = false;
        }

        // Process primes in decreasing order
        for (int i = seg_len - 1; i >= 0; i--) {
            if (!seg[i]) continue;
            ll p = seg_lo + i;
            total_large++;
            for (int k = 1; k < sz && m / k >= p; k++) {
                ll vp = (m / k) / p;
                hi[k] = (hi[k] + bc1 * lo[(int)vp]) % MOD;
            }
        }
    }

    printf("  large primes processed: %lld\n", total_large);
    return (getS(m) % MOD + MOD) % MOD;
}

int main() {
    printf("=== Project Euler #452: Long Products ===\n\n");

    // Verification
    struct { ll m; ll n; ll expected; } tests[] = {
        {10, 10, 571},
        {100, 100, 613658361},
        {1000, 1000, 1229737624},
        {10000, 10000, 1027202788},
        {100000, 100000, 602287876},
    };
    printf("Verification:\n");
    bool all_ok = true;
    for (auto& t : tests) {
        ll r = solve(t.m, t.n);
        bool ok = (r == t.expected);
        if (!ok) all_ok = false;
        printf("  F(%lld, %lld) = %lld  (expected %lld) [%s]\n",
               t.m, t.n, r, t.expected, ok ? "OK" : "FAIL");
    }
    printf(all_ok ? "All tests passed.\n\n" : "SOME TESTS FAILED!\n\n");

    // Main computation
    printf("Computing F(10^9, 10^9) mod %lld ...\n", MOD);
    clock_t t0 = clock();
    ll ans = solve(1000000000LL, 1000000000LL);
    double elapsed = (double)(clock() - t0) / CLOCKS_PER_SEC;
    printf("\n*** ANSWER: F(10^9, 10^9) mod %lld = %lld ***\n", MOD, ans);
    printf("Time: %.2f seconds\n", elapsed);

    return 0;
}