简体   繁体   中英

How to calculate smallest number with certain number of divisors?

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) = (a 1 +1)(a 2 +1) ... (a k +1)

where

n = p 1 a 1 * p 2 a 2 *p 3 a 3 *...*p k a k

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 p 1 x 1 * p 2 x 2 *p 3 x 3 *...*p k x k , where each of x i is between 0 and a i , so there are a i + 1 possibilities for choosing each of x i . 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) = (a 1 +1)(a 2 +1) ... (a k +1) = 16 = 2 4 .

This means that you have at most 4 different primes, therefore:

x = 2 a 1 * 3 a 2 *5 a 3 * 7 a 4

where a i >= 0. The question is now - in order to get minimum value of x , is it better to increase the powers of 2 (ie, increment a 1 ), or to use 7 (ie take a 4 =1 instead of a 4 =0)? Well, it's simple to check, 2*3*5*7 > 2 3 * 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, 2 2 , 3 2 , 2 4 etc. Why? Because 16 = 2 4 , so each of (a i +1) has to divide 16, ie it has to be power of 2. Every time when you add new power, it should increase the left hand side (ie, the variable d(x) ) by power of 2, since your final goal is to find the smallest number with 2 500500 divisors. Heap is initialized with first k primes (in the problem statement, k = 500500 ), and in each step, when you pop p x from the heap, p 2x is returned and result is multiplied by p x . Step-by-step solution for d(x) = 16 = 2 4 :

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:

  • Find the prime factor decomposition of the factorCount
  • Generate every unique combination of these prime factors
  • For each combination, extract these combination values from the original prime factor array and add one value to this array that is the extracted values multiplied together . Then sort elements in descending order.
  • For each array created by the previous step, check which yields the minimal number when computing 2^(b1-1)*3^(b2-1) 5^(b3-1) ...
  • This minimum number computed is the smallest number with 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.

The highest divisor any number has, other than itself, is the half of the number. For example, 120 has a max divisor of 60 other than itself. So, you can easily reduce the range from (n+1) to (n/2).

More over, for a number to have m divisors, the number must be atleast ((m-1) * 2) following the above logic (-1 because the m th number is itself). For example, a number with 4 divisors has to be atleast 6. So, your search for n has a smaller range now.

These two will reduce the runtime a little.

Not full answer some hints instead:

  1. the max integer divisor of n is n/2

    so no need to check all divisors up to n

  2. can use prime decomposition

    max prime divisor is sqrt(n) so no need to test up to n instead test up to sqrt(n) or up to number than has half of the n bits

    m=(2^(ceil(ceil(log2(n))/2)+1))-1

    that should speed up things a bit but you need to add the computation of non prime divisors

  3. this looks like some kind of series (prime decomposition)

     divisors | prime_divisors | non_prime_divisors | LCM(all divisors) 1 | 1 | | 1 2 | 1,2 | | 2 3 | 1,2 | 4 | 4 4 | 1,2,3 | 6 | 6 5 | 1,2 | 4,8,16 | 16 6 | 1,2,3 | 4,6,12 | 12 7 | 1,2 | 4,8,16,32,64 | 64 8 | 1,2,3 | 4,6,8,12,24 | 24 ... 16 | 1,2,3,5 |4,6,8,10,12,15,20,24,30,40,60,120 | 120

    so try to find the equation that generates this order and then simply compute the n-th iteration in modulo arithmetics (simple PI operation ... modmul ). I can see the even and odd elements have separate equation ...

[edit1] decomposition up to 16 divisors

  1:    1
  2:    1,   2
  3:    1,   2,   4
  4:    1,   2,   3,   6
  5:    1,   2,   4,   8,  16
  6:    1,   2,   3,   4,   6,  12
  7:    1,   2,   4,   8,  16,  32,  64
  8:    1,   2,   3,   4,   6,   8,  12,  24
  9:    1,   2,   3,   4,   6,   9,  12,  18,  36
 10:    1,   2,   3,   4,   6,   8,  12,  16,  24,  48
 11:    1,   2,   4,   8,  16,  32,  64, 128, 256, 512,1024
 12:    1,   2,   3,   4,   5,   6,  10,  12,  15,  20,  30,  60
 13:    1,   2,   4,   8,  16,  32,  64, 128, 256, 512,1024,2048,4096
 14:    1,   2,   3,   4,   6,   8,  12,  16,  24,  32,  48,  64,  96, 192
 15:    1,   2,   3,   4,   6,   8,   9,  12,  16,  18,  24,  36,  48,  72, 144
 16:    1,   2,   3,   4,   5,   6,   8,  10,  12,  15,  20,  24,  30,  40,  60, 120

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))

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)

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.

Fully functional code.

def find_smallest_number(num):
    number2=0

    if(num%8==0):
        number2=min(2**((num/4)-1)*3**1*5**1 , 2**((num//2)-1)*3**1)
    elif(num%9==0):
        number2=2**((num/9)-1)*3**2*5**2   
    elif(num%2==0 and num%3==0):
        number2=2**((num/6)-1)*3**2*5**1   
    elif((num%4==0)):
        number2=2**((num/4)-1)*3**1*5**1
    elif(num%2==0):
        number2=2**((num/2)-1)*3**1
    else:
        number2=2**(num-1)         

    return number2   


num=32
print("The number of divisors :",num)
result=find_smallest_number(num)
print("The smallest number having",num," divisors:",result)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM