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 smalle
The Prime Factorization (PF), in abbreviated form, for the smallest integer having 2**500,500 divisors is shown below:
2**31 * (3...7)**15 * (11...47)**7 * (53...2,713)**3 * (2,719...7,370,029)
The smallest positive integer having 2^n divisors is the product of the sequence of the first n primes and powers of primes. Powers of primes (pp) are primes raised to the 2^(2^e) power (powers are 2,4,8,16...). First 3 pp are 4,9 and 16 (2^2,3^2 and 2^4).
Since only prime factors are used in a PF, you will notice that the pp are aggregated with the prime factors. For n = 500,550 here are some stats or counts: 5 terms in the PF (powers 31,15,7,3,1). Total Primes (P) = 500,084 Powers of primes(pp) = 416 Total P + pp = n = 500,500 See theorem above.
Single primes (the last term of the PF) total 499,688.
You can calculate this PF (and others for 2^n) using Sage Math and Excel (although with Excel you'll need a prime counting function).
Also, you should check your PF solution. This can be done several ways by assigning weights to the various pp and single primes to check the original n.
Here is the high level gist of my Javascript - where factorCount
represents the number of divisors:
factorCount
number of divisorsHere'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.
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.
Phew, I just solved it in java. My "smallest number with 2^n divisors" ended up being represented as a map of primes and their powers.
This puzzle is as much about optimization as anything: get your code working and then refactor hard. It's definitely worth testing up to about 2^30 divisors as there is room for all sorts of second-order bugs to creep in when optimizing. Re-use the results of earlier calculations, try not to sort anything, and stop iterating as soon as you can (NavigableSet and LinkedHashMap were useful). I'm not going to teach my grandmother to efficiently test for primality.
I used java long throughout, but with numbers of this size it's easy to go over Long.MAX_VALUE in the middle of a calculation, remember: (A^2 * B) % C = (A * ((A * B) % C)) % C.
Hope all this motivates rather than gives the game away. Keep going!
This is my Code. You can use Sieve to generate Prime numbers. If you have any suggestions to my code please suggest.
def findfactors(num):
list1=[]
for i in range(num,1,-1):
if num%i==0:
list1.append(i)
return list1
def getcombinations(num):
factors=findfactors(num)
list1=[]
if num==1:
return 1
for i in factors:
if getcombinations(num//i)!=1:
list1.append([i,getcombinations(num//i)])
else:
list1.append(i)
return list1
def unloadlist(list1):
list2=[]
if type(list1).__name__=="list":
for i in list1[1]:
if type(i).__name__=="list":
i=unloadlist(i)
if type(i).__name__=="list":
flag=True
for j in i:
if type(j).__name__=="list":
list2.append([list1[0]]+j)
flag=False
if flag==True:
list2.append([list1[0]]+i)
else:
list2.append([list1[0],i])
if len(list2)==1:
list2=list2[0]
else:
list2=list1
return list2
def mergeitems(list1):
list2=[]
for i in list1:
if type(i).__name__=="int":
list2.append((i,))
elif type(i).__name__=="list":
if type(i[0]).__name__!="list":
list2.append(tuple(sorted(i,reverse=True)))
else:
for j in i:
list2.append(tuple(sorted(j,reverse=True)))
set1=set(list2)
return set1
def find_smallest_number(num):
#start writing your code here
list1=getcombinations(num)
for i in range(0,len(list1)):
list1[i]=unloadlist(list1[i])
mainlist=mergeitems(list1)
possibles=[]
primes=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227]
for i in mainlist:
num=1
for j in range(0,len(i)):
num*=primes[j]**(i[j] - 1)
possibles.append(num)
return min(possibles)
num=7
print("The number of divisors :",num)
result=find_smallest_number(num)
print("The smallest number having",num," divisors:",result)
As Miljen Mikic explains, the divisor counting function is determined by the prime factorisation. To calculate n, start at 1 and use a greedy algorithm to double the number of divisors k times, choosing the cheapest factor at each step. The initial costs are the prime numbers, replaced by their square when you use them. After precomputing the first k prime numbers, you can do this quickly with a min-heap. In Python
import primesieve # pip install primesieve
import heapq
def solve(k, modulus=None):
"""Calculate the smallest number with 2**k divisors."""
n = 1
costs = primesieve.generate_n_primes(k) # more than necessary
for i in range(k):
cost = heapq.heappop(costs)
heapq.heappush(costs, cost**2)
n *= cost
if modulus:
n %= modulus
return n
assert solve(4) == 120
if __name__ == "__main__":
print(solve(500500, 500500507))