From Project Euler 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.
It's simple enough to count the divisors of n, eg. in Python len([i for i in range(1,n+1) if n % i == 0])
. This is O(n).
I tried brute force search and found the smallest number with 32 divisors is 840, but it's much too slow for the problem above. From the inequality count_divisors(n) <= n
, the number is going to be massive.
What do you think? Any ideas? How to calculate smallest number with certain number of divisors?
Edit: I don't think this is a duplicate. This question is more specific, it concerns a particular class of much larger numbers. The other question asks generally. Its answers aren't applicable to this problem, it's a different magnitude.
You should use the formula for the number of divisors of integer n
:
d(n) = (a1+1)(a2+1)...(ak+1)
where
n = p1a1 * p2a2 *p3a3 *...*pkak
is a unique representation of every integer through powers of its prime divisors. This is a well-known formula, but if one wonders how to get it, note that d
divides n
if and only if d
is of the form p1x1 * p2x2 *p3x3 *...*pkxk, where each of xi is between 0 and ai, so there are ai + 1 possibilities for choosing each of xi. Now just apply the product rule and you get the desired formula.
For fixed d(n)
(as in your case), the minimum value of n
is obviously obtained by carefully selecting powers of existing primes, or by adding new primes. Let's work through this simple example, 16:
d(x) = (a1+1)(a2+1)...(ak+1) = 16 = 24.
This means that you have at most 4 different primes, therefore:
x = 2a1 * 3a2 *5a3 * 7a4
where ai >= 0. The question is now - in order to get minimum value of x
, is it better to increase the powers of 2 (i.e., increment a1), or to use 7 (i.e. take a4=1 instead of a4=0)? Well, it's simple to check, 2*3*5*7 > 23 * 3 * 5 = 120, that's why the 120 is answer in this case.
How to generalize this approach? You should create min-heap where you'll put the powers of primes, taking care that the number of divisors reaches the specified value. In case of 16, this min-heap would contain numbers 2, 3, 5, 7, 22, 32, 24 etc. Why? Because 16 = 24, so each of (ai+1) has to divide 16, i.e. it has to be power of 2. Every time when you add new power, it should increase the left hand side (i.e., the variable d(x)
) by power of 2, since your final goal is to find the smallest number with 2500500 divisors. Heap is initialized with first k
primes (in the problem statement, k = 500500
), and in each step, when you pop px from the heap, p2x is returned and result is multiplied by px. Step-by-step solution for d(x)
= 16 = 24:
Step Heap d(x) x
==========================
0 2,3,5,7 1 1
1 3,4,5,7 2 2
2 4,5,7,9 4 6
3 5,7,9,16 8 24
4 7,9,16,25 16 120
HTH.
Here is the high level gist of my Javascript - where factorCount
represents the number of divisors:
factorCount
number of divisors
Here's a high level code breakdown of my JavaScript functions:
var primeFactors = findPrimeFactors(factorCount);
var primeFactorCombinations = removeDuplicateArrays(generateCombinations(primeFactors, 1));
var combinedFactorCandidates = generateCombinedFactorCombinations(primeFactors, primeFactorCombinations);
var smallestNumberWithFactorCount = determineMinimumCobination(combinedFactorCandidates);
And here's the full sha-bang:
function smallestNumberByFactorCount(factorCount) {
function isPrime(primeCandidate) {
var p = 2;
var top = Math.floor(Math.sqrt(primeCandidate));
while(p<=top){
if(primeCandidate%p === 0){ return false; }
p++;
}
return true;
}
function findPrimeAfter(currentPrime) {
var nextPrimeCandidate = currentPrime + 1
while(true) {
if(isPrime(nextPrimeCandidate)){
return nextPrimeCandidate;
} else {
nextPrimeCandidate++;
}
}
}
function findPrimeFactors(factorParent) {
var primeFactors = [];
var primeFactorCandidate = 2;
while(factorParent !== 1){
while(factorParent % primeFactorCandidate === 0 && factorParent !== 1 ){
primeFactors.push(primeFactorCandidate);
factorParent /= primeFactorCandidate;
}
primeFactorCandidate = findPrimeAfter(primeFactorCandidate);
}
return primeFactors;
}
function sortArrayByValue(a,b){
return a-b;
}
function clone3DArray(arrayOfArrays) {
var cloneArray = arrayOfArrays.map(function(arr) {
return arr.slice();
});
return cloneArray;
}
function doesArrayOfArraysContainArray(arrayOfArrays, array){
var aOA = clone3DArray(arrayOfArrays);
var a = array.slice(0);
for(let i=0; i<aOA.length; i++){
if(aOA[i].sort().join(',') === a.sort().join(',')){
return true;
}
}
return false;
}
function removeDuplicateArrays(combinations) {
var uniqueCombinations = []
for(let c=0; c<combinations.length; c++){
if(!doesArrayOfArraysContainArray(uniqueCombinations, combinations[c])){
uniqueCombinations[uniqueCombinations.length] = combinations[c];
}
}
return uniqueCombinations;
}
function generateCombinations(parentArray, minComboLength) {
var generate = function(n, src, got, combinations) {
if(n === 0){
if(got.length > 0){
combinations[combinations.length] = got;
}
return;
}
for (let j=0; j<src.length; j++){
generate(n - 1, src.slice(j + 1), got.concat([src[j]]), combinations);
}
return;
}
var combinations = [];
for(let i=minComboLength; i<parentArray.length; i++){
generate(i, parentArray, [], combinations);
}
combinations.push(parentArray);
return combinations;
}
function generateCombinedFactorCombinations(primeFactors, primeFactorCombinations) {
var candidates = [];
for(let p=0; p<primeFactorCombinations.length; p++){
var product = 1;
var primeFactorsCopy = primeFactors.slice(0);
for(let q=0; q<primeFactorCombinations[p].length; q++){
product *= primeFactorCombinations[p][q];
primeFactorsCopy.splice(primeFactorsCopy.indexOf(primeFactorCombinations[p][q]), 1);
}
primeFactorsCopy.push(product);
candidates[candidates.length] = primeFactorsCopy.sort(sortArrayByValue).reverse();
}
return candidates;
}
function determineMinimumCobination (candidates){
var minimumValue = Infinity;
var bestFactorCadidate = []
for(let y=0; y<candidates.length; y++){
var currentValue = 1;
var currentPrime = 2;
for(let z=0; z<combinedFactorCandidates[y].length; z++){
currentValue *= Math.pow(currentPrime,(combinedFactorCandidates[y][z])-1);
currentPrime = findPrimeAfter(currentPrime);
}
if(currentValue < minimumValue){
minimumValue = currentValue;
bestFactorCadidate = combinedFactorCandidates[y];
}
}
return minimumValue;
}
var primeFactors = findPrimeFactors(factorCount);
var primeFactorCombinations = removeDuplicateArrays(generateCombinations(primeFactors, 1));
var combinedFactorCandidates = generateCombinedFactorCombinations(primeFactors, primeFactorCombinations);
var smallestNumberWithFactorCount = determineMinimumCobination(combinedFactorCandidates);
return smallestNumberWithFactorCount;
}
Paste the above code block into your browser console and you can test it out yourself:
> smallestNumberByFactorCount(3) --> 4
> smallestNumberByFactorCount(4) --> 6
> smallestNumberByFactorCount(5) --> 16
> smallestNumberByFactorCount(6) --> 12
> smallestNumberByFactorCount(16) --> 120
> smallestNumberByFactorCount(100) --> 45360
> smallestNumberByFactorCount(500) --> 62370000
> smallestNumberByFactorCount(5000) --> 4727833110000
> smallestNumberByFactorCount(100000000) --> 1.795646397225103e+40
My algorithm starts shitting the bed when the input gets up to around 100 million... so for Project Euler problem 500 where the input would be 2^500500 (a really, really, really big number) you would need another approach. However, this is a good general approach that gets you pretty far. Hope it helps.
Please leave comments with efficiency optimization suggestions. I would love to hear them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With