Eight Divisors
The eight divisors of 24 are 1, 2, 3, 4, 6, 8, 12, and 24. The ten numbers not exceeding 100 having exactly eight divisors are 24, 30, 40, 42, 54, 56, 66, 70, 78, and 88. Let f(n) denote the count...
Problem Statement
This archive keeps the full statement, math, and original media on the page.
The eight divisors of \(24\) are \(1, 2, 3, 4, 6, 8, 12\) and \(24\). The ten numbers not exceeding \(100\) having exactly eight divisors are \(24, 30, 40, 42, 54, 56, 66, 70, 78\) and \(88\).
Let \(f(n)\) be the count of numbers not exceeding \(n\) with exactly eight divisors.
You are given \(f(100) = 10\), \(f(1000) = 180\) and \(f(10^6) = 224427\).
Find \(f(10^{12})\).
Problem 501: Eight Divisors
Mathematical Development
Theorem 1 (Divisor Count Formula). Let be the canonical prime factorization of a positive integer . Then the number of positive divisors of is
Proof. Every divisor of has the form where for each . By the fundamental theorem of arithmetic, distinct exponent tuples yield distinct divisors. The number of valid tuples is by the multiplication principle.
Lemma 1 (Classification of integers with ). A positive integer satisfies if and only if belongs to exactly one of the following three families:
- Type A: for some prime .
- Type B: for distinct primes and .
- Type C: for distinct primes .
Proof. We require . Since each factor , we seek all multiplicative compositions of into parts . The only such compositions (up to reordering) are:
- : a single exponent , yielding .
- : two exponents , yielding with .
- : three exponents , yielding with distinct primes.
No other multiplicative partition of 8 into factors exists. The three families are mutually exclusive since the multiset of exponents uniquely determines the family.
Theorem 2 (Counting Formula). Let and let denote the prime-counting function. Then
where:
Proof. We count each family from Lemma 1 separately.
Case 1 (Type A). The condition is equivalent to . Since must be prime, the count is .
Case 2 (Type B). Fix a prime with . We count primes with . The number of primes is . Among these, is included if and only if , i.e., . Hence we subtract the indicator to exclude the case (which would give with ). Note that the pair and with yield the same product only if and swap roles, but when , so these are distinct integers counted under different values of the outer summation variable. Hence no double-counting occurs.
Case 3 (Type C). We impose the ordering to avoid overcounting. For each prime pair with , we require prime satisfying . The count of such is , which is nonnegative precisely when (since the smallest valid exceeds , so we need ).
The three families are mutually exclusive by Lemma 1, so .
Editorial
We case 2: n = p^3 * q, p != q. Finally, case 3: n = p * q * r with p < q < r.
Pseudocode
Case 1: n = p^7
Case 2: n = p^3 * q, p != q
Case 3: n = p * q * r with p < q < r
Complexity Analysis
Time. The dominant cost is constructing the Lucy Hedgehog prime-counting tables, which runs in time. The enumeration in Case 2 iterates over primes, each requiring an table lookup. The double enumeration in Case 3 iterates over pairs (by the prime counting estimate ), each with an lookup. Thus the total time is .
Space. The Lucy Hedgehog method maintains two arrays of size , plus the sieve of primes up to . Total space is .
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
// Lucy Hedgehog's prime counting function
// Returns pi(x) for any x, after preprocessing for values up to N
struct PrimeCount {
ll N;
int sqrtN;
vector<ll> small, large;
vector<int> primes;
vector<bool> sieve;
void init(ll n) {
N = n;
sqrtN = (int)sqrt((double)n);
while ((ll)sqrtN * sqrtN > n) sqrtN--;
while ((ll)(sqrtN + 1) * (sqrtN + 1) <= n) sqrtN++;
// Sieve primes up to sqrtN
sieve.assign(sqrtN + 1, true);
sieve[0] = sieve[1] = false;
primes.clear();
for (int i = 2; i <= sqrtN; i++) {
if (sieve[i]) {
primes.push_back(i);
for (ll j = (ll)i * i; j <= sqrtN; j += i)
sieve[j] = false;
}
}
// small[i] = count of primes <= i, for i = 0..sqrtN
// large[i] = count of primes <= N/i, for i = 1..sqrtN
small.assign(sqrtN + 1, 0);
large.assign(sqrtN + 1, 0);
for (int i = 1; i <= sqrtN; i++) {
small[i] = i - 1; // initially, all numbers >= 2
large[i] = N / i - 1;
}
for (int p : primes) {
ll pp = (ll)p * p;
if (pp > N) break;
ll pcnt_prev = small[p - 1]; // pi(p-1)
for (int i = 1; i <= sqrtN && (ll)i * pp <= N; i++) {
ll d = (ll)i * p;
if (d <= sqrtN)
large[i] -= large[d] - pcnt_prev;
else
large[i] -= small[N / d] - pcnt_prev;
}
for (int i = sqrtN; i >= pp; i--) {
small[i] -= small[i / p] - pcnt_prev;
}
}
}
ll count(ll x) {
if (x <= 0) return 0;
if (x <= sqrtN) return small[x];
ll idx = N / x;
if (idx <= sqrtN) return large[idx];
// Fallback -- shouldn't happen if x is of form N/k
return 0;
}
};
int main() {
ll N = 1000000000000LL; // 10^12
PrimeCount pc;
pc.init(N);
ll ans = 0;
// Case 1: p^7 <= N => p <= N^(1/7) ~ 51
{
int lim = (int)pow((double)N, 1.0 / 7.0);
while ((ll)pow(lim + 1, 7) <= N) lim++;
while ((ll)pow(lim, 7) > N) lim--;
// Count primes up to lim
for (int p : pc.primes) {
if (p > lim) break;
ll val = 1;
bool ok = true;
for (int j = 0; j < 7; j++) {
val *= p;
if (val > N) { ok = false; break; }
}
if (ok && val <= N) ans++;
}
}
// Case 2: p^3 * q <= N, p != q
{
// Sieve primes up to cbrt(N) ~ 10^4 for p
int plim = (int)cbrt((double)N);
while ((ll)(plim + 1) * (plim + 1) * (plim + 1) <= N) plim++;
// We need all primes up to plim (and beyond for q)
// Use pc.primes for small primes, and pc.count for pi(x)
int sieve_lim = max(plim + 1, pc.sqrtN);
vector<bool> sv(sieve_lim + 1, true);
sv[0] = sv[1] = false;
for (int i = 2; (ll)i * i <= sieve_lim; i++)
if (sv[i])
for (int j = i * i; j <= sieve_lim; j += i)
sv[j] = false;
for (int p = 2; p <= plim; p++) {
if (!sv[p]) continue;
ll p3 = (ll)p * p * p;
if (p3 >= N) break;
ll qlim = N / p3;
ll cnt = pc.count(qlim);
// Subtract 1 if p <= qlim (to remove q = p)
if (p <= qlim) cnt--;
ans += cnt;
}
}
// Case 3: p * q * r <= N, p < q < r, all distinct primes
{
int sieve_lim = pc.sqrtN;
vector<bool> sv(sieve_lim + 1, true);
sv[0] = sv[1] = false;
for (int i = 2; (ll)i * i <= sieve_lim; i++)
if (sv[i])
for (int j = i * i; j <= sieve_lim; j += i)
sv[j] = false;
vector<int> all_primes;
for (int i = 2; i <= sieve_lim; i++)
if (sv[i]) all_primes.push_back(i);
for (int i = 0; i < (int)all_primes.size(); i++) {
ll p = all_primes[i];
if (p * p * p >= N) break; // need q > p, r > q, so p*q*r > p^3
// Actually p*(p+1)*(p+2) >= p^3 approximately
for (int j = i + 1; j < (int)all_primes.size(); j++) {
ll q = all_primes[j];
ll pq = p * q;
if (pq * (q + 1) > N) break; // r > q needed
ll rlim = N / pq;
// r > q, r prime
ll cnt = pc.count(rlim) - pc.count(q);
if (cnt > 0) ans += cnt;
}
}
}
cout << ans << endl;
return 0;
}
def solve():
N = 10**12
# Compute floor(sqrt(N)) precisely
sqrtN = int(N**0.5)
while sqrtN * sqrtN > N:
sqrtN -= 1
while (sqrtN + 1) * (sqrtN + 1) <= N:
sqrtN += 1
# Lucy Hedgehog prime counting method
# small[i] = pi(i) for i <= sqrtN
# large[i] = pi(N // i) for 1 <= i <= sqrtN
small = list(range(-1, sqrtN + 1))
small[0] = 0
large = [0] * (sqrtN + 1)
for i in range(1, sqrtN + 1):
large[i] = N // i - 1
sieve = [True] * (sqrtN + 1)
primes = []
for p in range(2, sqrtN + 1):
if not sieve[p]:
continue
primes.append(p)
if p * p > N:
continue
pcnt = small[p - 1]
for i in range(1, sqrtN + 1):
if i * p * p > N:
break
d = i * p
if d <= sqrtN:
large[i] -= large[d] - pcnt
else:
large[i] -= small[N // d] - pcnt
for i in range(sqrtN, p * p - 1, -1):
small[i] -= small[i // p] - pcnt
def pi(x):
if x <= 0:
return 0
if x <= sqrtN:
return small[x]
idx = N // x
if idx <= sqrtN:
return large[idx]
return 0
ans = 0
# Case 1: p^7 <= N
for p in primes:
if p**7 > N:
break
ans += 1
# Case 2: p^3 * q <= N, p != q
for p in primes:
p3 = p * p * p
if p3 >= N:
break
qlim = N // p3
cnt = pi(qlim)
if p <= qlim:
cnt -= 1
ans += cnt
# Case 3: p < q < r, p*q*r <= N
for i, p in enumerate(primes):
if p * p * p >= N:
break
for j in range(i + 1, len(primes)):
q = primes[j]
pq = p * q
if pq * (q + 1) > N:
break
rlim = N // pq
cnt = pi(rlim) - pi(q)
if cnt > 0:
ans += cnt
print(ans)
solve()