Smooth Divisors of Binomial Coefficients
An integer is B-smooth if none of its prime factors is greater than B. Define S_B(n) as the largest B-smooth divisor of n. Examples: S_1(10) = 1 S_4(2100) = 12 S_17(2496144) = 5712 Define: F(n) = s...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
An integer is called \(B\)
Let \(S_B(n)\) be the largest \(B\)-smooth divisor of \(n\).
We can verify that \(S_1(10)=1\), \(S_4(2100) = 12\) and \(S_{17}(2496144) = 5712\)
Define \(\displaystyle F(n)=\sum _{B=1}^n \sum _{r=0}^n S_B(\binom n r)\). Here, \(\displaystyle \binom n r\) denotes the binomial coefficient.
Examples:
-
\(F(11) = 3132\)
-
\(F(1111) \mod 1\,000\,000\,993 = 706036312\)3
-
\(F(111\,111) \mod 1\,000\,000\,993 = 22156169\)
Find \(F(11\,111\,111) \mod 1\,000\,000\,993\).
Problem 468: Smooth Divisors of Binomial Coefficients
Approach
Key Insight: Legendre’s Formula
The exponent of prime p in C(n,r) is given by Legendre’s formula:
where s_p(n) is the sum of digits of n in base p (equivalently, the number of carries when adding r and n-r in base p).
Restructuring the Sum
For a prime p, S_B(C(n,r)) retains the contribution of p only if p <= B.
We can reorganize by considering each prime’s contribution. When B increases past a prime q, all primes up to q now contribute. So we only need to evaluate at B = prime values (since S_B doesn’t change between consecutive primes).
Approach: Contribution by Prime
Let primes up to n be p_1 < p_2 < … < p_k. For each B-value:
- S_B(C(n,r)) changes only when B crosses a prime
- Between consecutive primes p_i and p_{i+1}, S_B is constant
So:
where p_0 = 1 and p_{k+1} = n+1.
Computing the Inner Sum
For each threshold prime p_i, we need:
This can be computed using the digit representation of r in each prime base and the carry structure.
Lucas’ Theorem Connection
For a prime p, the p-adic valuation of C(n,r) depends on the carries when adding r and (n-r) in base p. Using a digit-DP approach on the base-p representation of n, we can compute the sum efficiently.
The key formula uses the factorization:
where n_j are the digits of n in base p and c_j(d) accounts for carries.
Actually, since carries propagate, this is not a simple product but requires a digit DP with carry tracking.
Complexity
- Sieve primes up to n: O(n / log n) primes
- For each prime p, digit DP on base-p representation: O(log_p(n)) digits
- Total: manageable for n = 11,111,111
Result
F(11,111,111) mod 1,000,000,993.
The modulus 1,000,000,993 is prime, which enables modular inverses.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
/*
* Project Euler Problem 468: Smooth Divisors of Binomial Coefficients
*
* F(n) = sum_{B=1}^{n} sum_{r=0}^{n} S_B(C(n,r))
* Find F(11111111) mod 1000000993.
*
* Approach:
* For each prime p <= n, compute sum_{r=0}^{n} p^{v_p(C(n,r))} using digit DP.
* Then combine using the multiplicative structure and grouping by B ranges.
*
* Key insight: v_p(C(n,r)) = number of carries when adding r and (n-r) in base p.
*
* Compile: g++ -O2 -o solution solution.cpp
*/
#include <iostream>
#include <vector>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
const long long MOD = 1000000993LL;
const int N = 11111111;
// Sieve of Eratosthenes
vector<int> sieve_primes(int limit) {
vector<bool> is_prime(limit + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; (long long)i * i <= limit; i++) {
if (is_prime[i]) {
for (int j = i * i; j <= limit; j += i)
is_prime[j] = false;
}
}
vector<int> primes;
for (int i = 2; i <= limit; i++)
if (is_prime[i]) primes.push_back(i);
return primes;
}
// Get digits of n in base p (LSB first)
vector<int> base_digits(long long n, int p) {
vector<int> digits;
if (n == 0) { digits.push_back(0); return digits; }
while (n > 0) {
digits.push_back(n % p);
n /= p;
}
return digits;
}
// Modular exponentiation
long long pow_mod(long long base, long long exp, long long mod) {
long long result = 1;
base %= mod;
while (exp > 0) {
if (exp & 1) result = result * base % mod;
exp >>= 1;
base = base * base % mod;
}
return result;
}
// Modular inverse using Fermat's little theorem (mod is prime)
long long mod_inv(long long a, long long mod) {
return pow_mod(a, mod - 2, mod);
}
/*
* Compute sum_{r=0}^{n} p^{v_p(C(n,r))} mod MOD
* using digit DP on the base-p representation of n.
*
* State: (carry, borrow) both in {0, 1}
* At each digit position, enumerate r_j from 0 to p-1.
*
* v_p(C(n,r)) = number of carries when adding r and (n-r) in base p (Kummer).
*/
long long sum_p_valuation(long long n, int p) {
vector<int> digits = base_digits(n, p);
int k = digits.size();
// dp[carry][borrow] -> sum value mod MOD
map<pair<int,int>, long long> dp;
dp[{0, 0}] = 1;
long long p_mod = p % MOD;
for (int j = 0; j < k; j++) {
int n_j = digits[j];
map<pair<int,int>, long long> new_dp;
for (auto& [state, val] : dp) {
if (val == 0) continue;
int carry = state.first;
int borrow = state.second;
for (int r_j = 0; r_j < p; r_j++) {
int raw = n_j - r_j - borrow;
int s_j, new_borrow;
if (raw < 0) {
s_j = raw + p;
new_borrow = 1;
} else {
s_j = raw;
new_borrow = 0;
}
int total = r_j + s_j + carry;
int new_carry;
long long contribution;
if (total >= p) {
new_carry = 1;
contribution = val * p_mod % MOD;
} else {
new_carry = 0;
contribution = val;
}
auto key = make_pair(new_carry, new_borrow);
new_dp[key] = (new_dp[key] + contribution) % MOD;
}
}
dp = new_dp;
}
return dp.count({0, 0}) ? dp[{0, 0}] : 0;
}
/*
* For each set of primes {p_1, ..., p_i}, we need
* G_i = sum_{r=0}^{n} prod_{j=1}^{i} p_j^{v_{p_j}(C(n,r))}
*
* The key insight for making this tractable:
*
* For LARGE primes p > n/2, v_p(C(n,r)) is at most 1 (it's 1 iff
* there's a carry at the single relevant digit position).
* For such primes, the contribution is simpler.
*
* For SMALL primes, the digit DP has more states but fewer primes to handle.
*
* The overall approach: process primes from largest to smallest.
* For each prime p, multiply the running product by the per-r factor p^{v_p}.
*
* Since we need the SUM over r of the PRODUCT, and the factors for different
* primes are NOT independent (they all depend on the same r), we cannot simply
* multiply individual sums.
*
* Advanced approach: For each B-interval, compute G_i using a combined digit DP
* that tracks carry/borrow states for ALL primes simultaneously.
* This is only feasible if the number of small primes is manageable.
*
* Alternative: Use the formula for sum_{r} prod_p p^{v_p(C(n,r))} based on
* the factorization structure of C(n,r).
*
* S_B(C(n,r)) = C(n,r) / (largest divisor of C(n,r) using only primes > B)
*
* Let T_B(n,r) = C(n,r) / S_B(C(n,r)) = product of p^{v_p(C(n,r))} for p > B.
* Then S_B(C(n,r)) = C(n,r) / T_B(n,r).
*
* sum_r S_B(C(n,r)) = sum_r C(n,r) / T_B(n,r)
* = 2^n * E[1/T_B] where the expectation is over uniform r
*
* This doesn't obviously simplify either.
*
* For the actual competition solution, one typically uses:
* F(n) = sum_{B=1}^{n} sum_{r=0}^{n} S_B(C(n,r))
* = sum_{r=0}^{n} sum_{B=1}^{n} S_B(C(n,r))
*
* For fixed r, as B increases, S_B(C(n,r)) is non-decreasing and changes
* at B = prime. Specifically:
* sum_{B=1}^{n} S_B(C(n,r)) = sum_{B=1}^{n} prod_{p<=B} p^{v_p(C(n,r))}
*
* = (p_1 - 1) * 1 + sum_{i=1}^{k} (p_{i+1} - p_i) * prod_{j<=i} p_j^{v_{p_j}}
*
* where p_0 = 1 and p_{k+1} = n+1.
*
* = sum_{i=0}^{k} gap_i * prod_{j<=i} p_j^{v_{p_j}(C(n,r))}
*
* Then F(n) = sum_r sum_i gap_i * prod_{j<=i} p_j^{v_{p_j}}
* = sum_i gap_i * sum_r prod_{j<=i} p_j^{v_{p_j}}
* = sum_i gap_i * G_i
*
* Now the challenge is computing G_i for each i.
*
* One useful property: G_i can be computed incrementally.
* G_0 = n + 1 (product over empty set = 1 for each r)
* G_i = sum_r prod_{j=1}^{i} p_j^{v_{p_j}}
*
* Unfortunately G_i != G_{i-1} * (something simple) because the
* new prime's valuation is correlated with previous ones through r.
*
* For large primes p > sqrt(n), v_p(C(n,r)) can be computed from
* just the top few digits of r in base p.
*
* This is an advanced competition problem. Below we implement the
* framework and compute what we can.
*/
int main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL);
cout << "Project Euler 468: Smooth Divisors of Binomial Coefficients" << endl;
cout << "N = " << N << ", MOD = " << MOD << endl;
vector<int> primes = sieve_primes(N);
int k = primes.size();
cout << "Number of primes up to " << N << ": " << k << endl;
// Compute individual prime sums for analysis
cout << "\nPer-prime sums (sum_r p^{v_p(C(N,r))}):" << endl;
for (int idx = 0; idx < min(k, 10); idx++) {
int p = primes[idx];
long long s = sum_p_valuation(N, p);
cout << " p = " << p << ": " << s << endl;
}
// Compute gap values
cout << "\nComputing F(N) using gap decomposition..." << endl;
// For the full solution, we would need to compute G_i for each prime index.
// This requires the joint distribution approach described above.
//
// For primes p > N/2, v_p(C(N,r)) is either 0 or 1, and equals 1 iff
// floor(r/p) + floor((N-r)/p) < floor(N/p), i.e., there's a carry.
// The number of such r can be computed combinatorially.
//
// For the largest primes (p > N/2 and p <= N), floor(N/p) = 1,
// and v_p = 1 iff r mod p + (N-r) mod p >= p.
// For p > N/2: the digits of N in base p are [N mod p, 1].
// carry occurs when r_0 + (N_0 - r_0 - borrow) >= p considering borrow.
//
// This approach processes ~half the primes quickly. The remaining small
// primes require more careful treatment.
// For large primes > N/2:
long long large_prime_contribution = 0;
int large_count = 0;
for (int idx = k - 1; idx >= 0 && primes[idx] > N / 2; idx--) {
int p = primes[idx];
// For p > N/2: C(N, r) is divisible by p iff p <= N and
// r is not 0, not N, and not a multiple of p that works.
// Actually v_p(C(N,r)) = 1 if r mod p > N mod p, else 0.
// (when N/p = 1, i.e., p > N/2)
int N_mod_p = N % p;
// Number of r in [0, N] with v_p = 1:
// r_0 > N_mod_p where r_0 = r mod p, but r ranges [0, N].
// For r in [0, p-1]: count with r_0 > N_mod_p = p - 1 - N_mod_p
// For r in [p, N]: r = p + r', r' in [0, N-p].
// N - r = N - p - r', digits: [(N-p) mod p ... ] = [N_mod_p, 0]
// Actually for r >= p and p > N/2: r can be at most N, and r >= p > N/2
// so N - r < N/2 < p. Digit of r in base p: [r mod p, 1], digit of N-r: [N-r, 0].
// Wait, N-r < p, so N-r has a single digit.
// r has digits [r mod p, 1]. N-r has digits [N-r, 0].
// Adding: position 0: (r mod p) + (N - r) + carry_0.
// But carry_0 = 0 initially. Sum = r mod p + N - r.
// If r >= p: r mod p = r - p. Sum at pos 0 = (r - p) + (N - r) = N - p = N_mod_p.
// Since N_mod_p < p, no carry. carry_1 = 0.
// Position 1: 1 + 0 + 0 = 1 < p, no carry.
// So v_p = 0 for r >= p.
//
// For r < p: r mod p = r. N - r digits: if N - r >= p, then [N-r mod p, 1]. Else [(N-r), 0].
// N - r >= p iff r <= N - p = N_mod_p. So:
// Case r <= N_mod_p: N-r >= p. r digits [r, 0], (N-r) digits [(N-r) mod p, 1] = [N-r-p, 1]
// Wait N-r-p = N_mod_p - r. Position 0: r + (N_mod_p - r) = N_mod_p < p. No carry.
// Position 1: 0 + 1 = 1 < p. No carry. v_p = 0.
// Case r > N_mod_p: N-r < p. r digits [r, 0], (N-r) digits [N-r, 0].
// Position 0: r + (N-r) = N. If N >= p, carry. N = p + N_mod_p. So N >= p always.
// carry = 1, digit = N - p = N_mod_p.
// Position 1: 0 + 0 + 1 = 1 < p. No carry.
// v_p = 1.
//
// So for p > N/2: v_p(C(N,r)) = 1 iff 0 <= r < p and r > N_mod_p.
// Count = p - 1 - N_mod_p.
int count_v1 = p - 1 - N_mod_p;
int count_v0 = (N + 1) - count_v1;
// sum_r p^{v_p(C(N,r))} = count_v0 * 1 + count_v1 * p
// = count_v0 + count_v1 * p
// Already computed above individually; this is for understanding.
large_count++;
}
cout << "Primes > N/2: " << large_count << " out of " << k << endl;
// The full solution would continue with the incremental computation.
// Due to the complexity, we output the framework and note that
// the answer requires the full joint-prime digit DP.
cout << "\nNote: Full computation of F(11111111) mod 1000000993 requires" << endl;
cout << "the complete joint prime analysis. See solution.md for details." << endl;
// Verify small case n=11
{
cout << "\n=== Verification: n=11 ===" << endl;
vector<int> p11 = sieve_primes(11);
cout << "Primes up to 11: ";
for (int p : p11) cout << p << " ";
cout << endl;
// Brute force for n=11
// C(11, r) for r = 0..11: 1 1 55 165 330 462 462 330 165 55 11 1
long long F11 = 0;
int binom11[] = {1, 11, 55, 165, 330, 462, 462, 330, 165, 55, 11, 1};
for (int B = 1; B <= 11; B++) {
for (int r = 0; r <= 11; r++) {
int c = binom11[r];
int sb = 1;
for (int p : p11) {
if (p > B) break;
int cc = c;
int e = 0;
while (cc % p == 0) { cc /= p; e++; }
for (int x = 0; x < e; x++) sb *= p;
}
F11 += sb;
}
}
cout << "F(11) = " << F11 << " (expected 3132)" << endl;
}
return 0;
}
"""
Project Euler Problem 468: Smooth Divisors of Binomial Coefficients
S_B(n) = largest B-smooth divisor of n
F(n) = sum_{B=1}^{n} sum_{r=0}^{n} S_B(C(n,r))
Find F(11111111) mod 1000000993.
"""
import sys
from collections import defaultdict
MOD = 1_000_000_993
def sieve_primes(limit):
"""Return list of primes up to limit."""
is_prime = [True] * (limit + 1)
is_prime[0] = is_prime[1] = False
for i in range(2, int(limit**0.5) + 1):
if is_prime[i]:
for j in range(i*i, limit + 1, i):
is_prime[j] = False
return [i for i in range(2, limit + 1) if is_prime[i]]
def base_digits(n, p):
"""Return digits of n in base p, least significant first."""
if n == 0:
return [0]
digits = []
while n > 0:
digits.append(n % p)
n //= p
return digits
def pow_mod(base, exp, mod):
"""Fast modular exponentiation."""
result = 1
base %= mod
while exp > 0:
if exp & 1:
result = result * base % mod
exp >>= 1
base = base * base % mod
return result
def compute_sum_p_valuation(n, p, mod):
"""
Compute sum_{r=0}^{n} p^{v_p(C(n,r))} mod `mod`.
v_p(C(n,r)) = (s_p(r) + s_p(n-r) - s_p(n)) / (p-1)
where s_p(k) = digit sum of k in base p.
Equivalently, v_p(C(n,r)) = number of carries when adding r and (n-r) in base p.
We use digit DP: process digits of n in base p from least significant.
State: carry_in (0 or 1), tracking sum of p^(carries).
At each digit position j with n_j = digit of n:
- Choose r_j in [0, n_j + carry_in] considering carry
- r_j is the j-th digit of r in base p
- carry_out = 1 if r_j + (n_j - r_j + carry_in_from_complement) >= p
Actually, let's think about it differently.
When adding r and (n-r) in base p:
- digit of r: d
- digit of (n-r): depends on borrows
A cleaner approach: use the Kummer's theorem interpretation directly.
v_p(C(n,r)) = number of carries when adding r and n-r in base p.
Let n have digits n_0, n_1, ..., n_k in base p (LSB first).
For r with digits r_0, r_1, ..., r_k:
- At position j, the sum r_j + (n_j - r_j) in the absence of carries would be n_j
- With carry c_j from position j-1: we compute r_j + s_j + c_j where s_j = n_j - r_j adjusted
Let me use a different DP formulation.
Let c_j be the carry into position j (c_0 = 0).
At position j: r_j can be 0..p-1 but we need 0 <= r_j <= ... valid.
The digit of n-r at position j: (n_j - r_j - borrow_j) mod p
borrow_{j+1} = 1 if n_j - r_j - borrow_j < 0
Then carry when adding r and (n-r) at position j:
r_j + ((n_j - r_j - borrow_j) mod p) + carry_j
= r_j + (n_j - r_j - borrow_j + p * borrow_{j+1}) + carry_j
= n_j - borrow_j + p * borrow_{j+1} + carry_j
If this >= p, carry_{j+1_add} = 1, else 0.
But this equals n_j - borrow_j + p * borrow_{j+1} + carry_j.
It turns out carry_{j+1_add} = borrow_{j+1} when everything is consistent.
Total carries = sum of carry_{j+1_add} for each position = v_p(C(n,r)).
Simpler approach using Kummer's theorem:
v_p(C(n,r)) = number of carries when adding r and n-r in base p.
This equals the number of positions j where r_j + (n-r)_j + c_j >= p.
DP state: (position j, carry c in {0,1}), value = sum of p^(carries so far).
Transition at position j with carry c:
For each valid r_j (0 to p-1), determine (n-r)_j and new carry.
We need r <= n, so we track borrow for the subtraction n - r as well.
This adds another state dimension: (position, carry_add, borrow_sub).
Both carry_add and borrow_sub are in {0, 1}, so 4 states per position.
At position j:
- n_j is the j-th digit of n in base p
- r_j ranges from 0 to p-1
- s_j = (n_j - r_j - borrow_sub) -- the "raw" difference
- If s_j < 0: actual digit = s_j + p, new borrow = 1
- Else: actual digit = s_j, new borrow = 0
- sum_j = r_j + actual_s_j + carry_add
- If sum_j >= p: new carry = 1, this position contributes a carry
- Else: new carry = 0
When a carry occurs, multiply accumulated value by p.
DP[j][carry][borrow] = sum over valid r choices of p^(carries up to position j)
"""
digits = base_digits(n, p)
k = len(digits) # number of digit positions
# dp[carry][borrow] = accumulated sum mod `mod`
dp = defaultdict(int)
dp[(0, 0)] = 1 # initial state: no carry, no borrow
p_mod = p % mod
for j in range(k):
n_j = digits[j]
new_dp = defaultdict(int)
for (carry, borrow), val in dp.items():
if val == 0:
continue
for r_j in range(p):
raw = n_j - r_j - borrow
if raw < 0:
s_j = raw + p
new_borrow = 1
else:
s_j = raw
new_borrow = 0
total = r_j + s_j + carry
if total >= p:
new_carry = 1
# This position has a carry, multiply by p
new_dp[(new_carry, new_borrow)] = (
new_dp[(new_carry, new_borrow)] + val * p_mod
) % mod
else:
new_carry = 0
new_dp[(new_carry, new_borrow)] = (
new_dp[(new_carry, new_borrow)] + val
) % mod
dp = new_dp
# Final state must have carry=0 and borrow=0 (since r <= n means no final borrow,
# and the addition r + (n-r) = n has no final carry)
result = dp.get((0, 0), 0)
return result % mod
def solve_small(n):
"""Brute force for small n to verify."""
from math import comb
def largest_smooth_divisor(num, B):
"""Largest B-smooth divisor of num."""
if num == 0:
return 0
result = 1
for p in range(2, B + 1):
if not all(p % d != 0 for d in range(2, p)):
continue # skip non-primes
# Check if p is prime
is_p = True
for d in range(2, int(p**0.5) + 1):
if p % d == 0:
is_p = False
break
if not is_p:
continue
while num % p == 0:
result *= p
num //= p
return result
def is_prime(x):
if x < 2:
return False
for d in range(2, int(x**0.5) + 1):
if x % d == 0:
return False
return True
primes = [p for p in range(2, n + 1) if is_prime(p)]
total = 0
for B in range(1, n + 1):
for r in range(n + 1):
c = comb(n, r)
sb = 1
temp = c
for p in primes:
if p > B:
break
while temp % p == 0:
sb *= p
temp //= p
temp = c
# Recompute properly
sb = 1
for p in primes:
if p > B:
break
cc = c
while cc % p == 0:
sb *= p
cc //= p
total += sb
return total
def solve_small_v2(n):
"""Correct brute force."""
from math import comb
def is_prime(x):
if x < 2: return False
for d in range(2, int(x**0.5) + 1):
if x % d == 0: return False
return True
primes = [p for p in range(2, n + 1) if is_prime(p)]
total = 0
for B in range(1, n + 1):
smooth_primes = [p for p in primes if p <= B]
for r in range(n + 1):
c = comb(n, r)
sb = 1
for p in smooth_primes:
while c % p == 0:
sb *= p
c //= p
c = comb(n, r) # reset
# Actually need to extract all factors of each prime
sb = 1
for p in smooth_primes:
cc = comb(n, r)
e = 0
while cc % p == 0:
cc //= p
e += 1
sb *= p ** e
total += sb
return total
def solve_optimized(n, mod):
"""
Optimized solution using per-prime digit DP.
F(n) = sum_{B=1}^{n} sum_{r=0}^{n} S_B(C(n,r))
= sum_{B=1}^{n} sum_{r=0}^{n} prod_{p<=B, p prime} p^{v_p(C(n,r))}
Rearranging: let primes be p_1 < p_2 < ... < p_k (all primes <= n).
Define G_i = sum_{r=0}^{n} prod_{j=1}^{i} p_j^{v_{p_j}(C(n,r))}
Then F(n) = sum_{i=0}^{k} gap_i * G_i
where gap_i = (p_{i+1} - p_i) for 0 <= i < k, with p_0 = 1 and gap for
the last segment is (n - p_k + 1) if p_k <= n, plus gap for B values
between p_k and n.
Wait, let's be more careful. For B from 1 to n:
- For B in [1, p_1-1]: S_B(C(n,r)) = 1 (no primes <= B)
- For B in [p_1, p_2-1]: S_B uses only p_1
- For B in [p_i, p_{i+1}-1]: S_B uses p_1,...,p_i
- For B in [p_k, n]: S_B uses all primes
So: F(n) = (p_1 - 1) * (n+1) [since S_B = 1 for all r]
+ sum_{i=1}^{k} (next - p_i) * G_i
where next = p_{i+1} for i < k, and next = n+1 for i = k.
Problem: G_i is the product over multiple primes, and the r-sum doesn't
factor into independent per-prime sums because different primes' valuations
of C(n,r) are NOT independent.
However, for C(n,r), using Kummer's theorem, v_p(C(n,r)) depends on the
base-p representation of r. Since different primes have different bases,
the joint distribution over r of (v_{p1}, v_{p2}, ...) is complex.
For the full solution, we need a different approach or accept that
the direct computation is infeasible in Python for n=11111111.
"""
primes = sieve_primes(n)
k = len(primes)
# For each prime p, compute: for each r in 0..n, p^{v_p(C(n,r))}
# This is what compute_sum_p_valuation does: sum_r p^{v_p(C(n,r))}
# But we need the PRODUCT over primes, summed over r.
# Since different primes' valuations are independent when viewed via CRT
# on the digits of r... actually they ARE independent!
# Key insight: v_p(C(n,r)) depends on the base-p digits of r.
# v_q(C(n,r)) depends on the base-q digits of r.
# For different primes p and q, knowing the base-p digits of r tells
# nothing about the base-q digits (by CRT, for r in a suitable range).
# BUT r ranges from 0 to n, not 0 to p*q*..., so they're not fully
# independent. However, for large n, the dependence is weak.
# Actually, the correct approach uses the multiplicativity more carefully.
# For each B, S_B(C(n,r)) = prod_{p<=B} p^{v_p(C(n,r))}.
# Sum over r: sum_r prod_{p<=B} p^{v_p(C(n,r))}.
# This is NOT simply the product of individual sums because the product
# is inside the sum. The valuations for different primes are correlated
# through r.
# The efficient approach for this problem uses the following:
# For each prime p, define f_p(r) = p^{v_p(C(n,r))}.
# Then S_B(C(n,r)) = prod_{p<=B} f_p(r).
# By Dirichlet convolution / multiplicative function theory on C(n,r),
# there's a way to compute this efficiently.
# For now, implement a working solution for small n and provide the
# framework for the full solution.
print(f"Number of primes up to {n}: {k}")
print("Computing per-prime sums...")
# Compute individual prime sums (these are needed even if not the full answer)
prime_sums = {}
for p in primes[:20]: # Just first few for demonstration
s = compute_sum_p_valuation(n, p, mod)
prime_sums[p] = s
print(f" sum_r {p}^v_{p}(C({n},r)) = {s} (mod {mod})")
return prime_sums
def create_visualization(n_small=11):
"""Create visualization for small case."""
from math import comb
def is_prime(x):
if x < 2: return False
for d in range(2, int(x**0.5) + 1):
if x % d == 0: return False
return True
primes = [p for p in range(2, n_small + 1) if is_prime(p)]