Problem 500!!!
The number of divisors of 120 is 16. In fact, 120 is the smallest number having 16 divisors. Find the smallest number with 2^500500 divisors. Give your answer modulo 500500507.
Problem Statement
This archive keeps the full statement, math, and original media on the page.
The number of divisors of \(120\) is \(16\).
In fact \(120\) is the smallest number having \(16\) divisors.
Find the smallest number with \(2^{500500}\) divisors.
Give your answer modulo \(500500507\).
Problem 500: Problem 500!!!
Mathematical Analysis
Divisor Count Formula
For a number , the number of divisors is:
We need , which means each factor must be a power of 2. So each exponent must be of the form for some .
Greedy Strategy
To minimize , we assign larger exponents to smaller primes. Each “doubling step” either:
- Introduces a new prime with exponent 1 (multiplying by ), contributing one factor of 2 to .
- Doubles the exponent of an existing prime to (multiplying by ), contributing one additional factor of 2 to .
We need exactly 500500 such doubling steps. At each step, we greedily pick the option that multiplies by the smallest value.
Priority Queue Approach
We maintain a min-heap of candidate values:
- Initially, all primes are candidates with value (adding prime with exponent 1).
- When we pick prime at level (meaning ), we push as the next candidate for that prime.
We perform 500500 iterations, always picking the smallest candidate.
Editorial
We generate primes up to ~7500000 (about 500500 primes needed). We then initialize a min-heap with all these primes. Finally, return the answer.
Pseudocode
Generate primes up to ~7500000 (about 500500 primes needed)
Initialize a min-heap with all these primes
For i = 1 to 500500:
Return the answer
Correctness
Theorem. The method described above computes exactly the quantity requested in the problem statement.
Proof. The preceding analysis identifies the admissible objects and derives the formula, recurrence, or exhaustive search carried out by the algorithm. The computation evaluates exactly that specification, so every valid contribution is included once and no invalid contribution is counted. Therefore the returned value is the required answer.
Complexity Analysis
- Time: where , due to heap operations.
- Space: for the heap and prime sieve.
Answer
Code
Each problem page includes the exact C++ and Python source files from the local archive.
#include <bits/stdc++.h>
using namespace std;
const long long MOD = 500500507;
const int TARGET = 500500;
long long power(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;
base = base * base % mod;
exp >>= 1;
}
return result;
}
int main() {
// Sieve primes up to ~7.6 million (need about 500500 primes)
const int LIMIT = 7800000;
vector<bool> is_prime(LIMIT + 1, true);
is_prime[0] = is_prime[1] = false;
for (int i = 2; 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);
}
// Min-heap: (value, base_prime)
// We store log values for comparison, actual modular product separately
// Actually, we can use double for comparison and track modular arithmetic
// Better: use a priority queue of (log_value, prime, current_power_of_two)
priority_queue<pair<double, int>, vector<pair<double, int>>, greater<>> pq;
int prime_idx = 0;
// Push first prime
pq.push({log((double)primes[0]), 0});
prime_idx = 1;
long long answer = 1;
for (int i = 0; i < TARGET; i++) {
// Ensure we have enough primes in the queue
while (prime_idx < (int)primes.size() &&
(pq.empty() || log((double)primes[prime_idx]) <= pq.top().first + 1e-9 || prime_idx <= i + 1)) {
pq.push({log((double)primes[prime_idx]), prime_idx});
prime_idx++;
}
auto [log_val, idx] = pq.top();
pq.pop();
// Multiply answer by primes[idx]^(2^j) mod MOD
// The log_val encodes the actual value; the modular multiplication:
// We need to compute primes[idx]^(round(exp(log_val)/1)) but that's imprecise.
// Better approach: store the actual prime and exponent level
// Actually, let's redo this properly
// Store (log_value, prime_index, level) where the actual multiplier is prime^(2^level)
// and log_value = 2^level * log(prime)
// For now, compute modular value from prime and level
// We need to track level. Let me restart with a cleaner approach.
break;
}
// Clean approach: store (log_value, prime, level)
// where multiplier = prime^(2^level), log_value = 2^level * log(prime)
struct Entry {
double log_val;
int prime_idx;
int level;
bool operator>(const Entry& o) const { return log_val > o.log_val; }
};
priority_queue<Entry, vector<Entry>, greater<Entry>> pq2;
for (int i = 0; i < min((int)primes.size(), TARGET + 100); i++) {
pq2.push({log((double)primes[i]), i, 0});
}
answer = 1;
for (int i = 0; i < TARGET; i++) {
Entry e = pq2.top();
pq2.pop();
// Multiplier is primes[e.prime_idx]^(2^e.level)
long long mult = power(primes[e.prime_idx], 1LL << e.level, MOD);
answer = answer * mult % MOD;
// Push next level
pq2.push({e.log_val * 2, e.prime_idx, e.level + 1});
}
cout << answer << endl;
return 0;
}
import heapq
import math
def solve():
MOD = 500500507
TARGET = 500500
# Sieve primes
LIMIT = 7800000
is_prime = bytearray(b'\x01') * (LIMIT + 1)
is_prime[0] = is_prime[1] = 0
for i in range(2, int(LIMIT**0.5) + 1):
if is_prime[i]:
is_prime[i*i::i] = bytearray(len(is_prime[i*i::i]))
primes = [i for i in range(2, LIMIT + 1) if is_prime[i]]
# Min-heap: (log_value, prime_index, level)
# multiplier = prime^(2^level), log_value = 2^level * log(prime)
heap = []
for i in range(min(len(primes), TARGET + 100)):
heapq.heappush(heap, (math.log(primes[i]), i, 0))
answer = 1
for _ in range(TARGET):
log_val, idx, level = heapq.heappop(heap)
# Multiply by primes[idx]^(2^level) mod MOD
mult = pow(primes[idx], 1 << level, MOD)
answer = answer * mult % MOD
# Push next level
heapq.heappush(heap, (log_val * 2, idx, level + 1))
print(answer)
solve()