web-dev-qa-db-fra.com

Comment calculer le plus petit nombre avec un certain nombre de diviseurs?

De Projet Euler problem 500

Le nombre de diviseurs de 120 est 16. En fait, 120 est le plus petit nombre avec 16 diviseurs.

Trouvez le plus petit nombre avec 2 ** 500500 diviseurs. Donnez votre réponse modulo 500500507.

C'est assez simple de compter les diviseurs de n, par exemple. en Python len([i for i in range(1,n+1) if n % i == 0]). C'est O (n).

J'ai essayé la recherche par force brute et j'ai trouvé que le plus petit nombre avec 32 diviseurs est 840, mais c'est beaucoup trop lent pour le problème ci-dessus. De l'inégalité count_divisors(n) <= n, le nombre va être énorme.

Qu'est-ce que tu penses? Des idées? Comment calculer le plus petit nombre avec un certain nombre de diviseurs?


Edit: Je ne pense pas que ce soit un doublon. Cette question est plus spécifique, elle concerne une classe particulière de beaucoup plus grands nombres. La autre question demande en général. Ses réponses ne sont pas applicables à ce problème, c'est une ampleur différente.

14
Colonel Panic

Vous devriez utiliser la formule pour le nombre de diviseurs de nombre entier n:

d(n) = (a1+1) (a2+1) ... (ak+1)

n = p1une1 * p2une2 * p3une3 * ... * pkunek

est une représentation unique de chaque entier grâce aux puissances de ses principaux diviseurs. Ceci est une formule bien connue, mais si on se demande comment l'obtenir, notez que d divise n si et seulement si d est de la forme p1x1 * p2x2 * p3x3 * ... * pkxk, où chacun de xje est compris entre 0 et aje, donc il y aje + 1 possibilités pour choisir chacun des xje. Maintenant, appliquez simplement la règle de produit et vous obtenez la formule souhaitée.

Pour d(n) fixe (comme dans votre cas), la valeur minimale de n est évidemment obtenue en sélectionnant avec soin les puissances des nombres premiers existants ou en ajoutant de nouveaux nombres premiers. Travaillons à travers cet exemple simple, 16:

d (x) = (a1+1) (a2+1) ... (ak+1) = 16 = 24.

Cela signifie que vous avez au plus 4 nombres premiers différents, donc:

x = 2une1 * 3une2 * 5une3 * 7une4

où unje > = 0. La question est maintenant - pour obtenir la valeur minimale de x, vaut-il mieux augmenter les puissances de 2 (c.-à-d., Incrémenter a1), ou d’utiliser 7 (c’est-à-dire prendre4= 1 au lieu d'un4= 0)? Eh bien, c'est simple à vérifier, 2 * 3 * 5 * 7> 23 * 3 * 5 = 120, c'est pourquoi le 120 est la réponse dans ce cas.

Comment généraliser cette approche? Vous devez créer min-heap où vous placerez les puissances des nombres premiers, en veillant à ce que le nombre de diviseurs atteigne la valeur spécifiée. Dans le cas de 16, ce min-tas contiendrait les nombres 2, 3, 5, 7, 22, 32, 24 etc. Pourquoi? Parce que 16 = 24, donc chacun de (unje+1) doit diviser 16, c'est-à-dire qu'il doit avoir une puissance de 2. Chaque fois que vous ajoutez un nouveau pouvoir, le côté gauche (c'est-à-dire la variable d(x)) doit être augmenté de 2, trouver le plus petit nombre avec 2500500 diviseurs. Le segment de mémoire est initialisé avec les premiers nombres premiers k (dans l'instruction de problème, k = 500500), et à chaque étape, lorsque vous ouvrez px du tas, p2x est retourné et le résultat est multiplié par px. Solution pas à pas pour 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.

10
Miljen Mikic

Voici le résumé de mon Javascript - où factorCount représente le nombre de diviseurs:

  • Trouver la décomposition en facteurs premiers du facteurCount
  • Générer chaque combinaison unique de ces facteurs premiers
  • Pour chaque combinaison, extrayez ces valeurs de combinaison du tableau de facteurs premiers d'origine et ajoutez une valeur à ce tableau, qui correspond aux valeurs extraites multipliées ensemble . Ensuite, triez les éléments dans l'ordre décroissant.
  • Pour chaque tableau créé à l'étape précédente, cochez le nombre minimal lors du calcul de 2 ^ (b1-1) * 3 ^ (b2-1) 5 ^ (b3-1) ...
  • Ce nombre minimum calculé est le le plus petit nombre avec factorCount nombre de diviseurs

Voici une description détaillée du code de mes fonctions JavaScript:

var primeFactors = findPrimeFactors(factorCount);
var primeFactorCombinations = removeDuplicateArrays(generateCombinations(primeFactors, 1));
var combinedFactorCandidates = generateCombinedFactorCombinations(primeFactors, primeFactorCombinations);
var smallestNumberWithFactorCount = determineMinimumCobination(combinedFactorCandidates);

Et voici le sha-bang complet:

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;
}

Collez le bloc de code ci-dessus dans la console de votre navigateur et vous pourrez le tester vous-même:

> 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

Mon algorithme commence à chier lorsque l'entrée atteint environ 100 millions ... Pour le problème Euler 500 du projet Euler où l'entrée serait 2 ^ 500500 (un très grand nombre), vous auriez besoin d'une autre approche. Cependant, c'est une bonne approche générale qui vous mène assez loin. J'espère que ça aide.

S'il vous plaît laissez des commentaires avec des suggestions d'optimisation de l'efficacité. J'aimerais les entendre.

1
Cumulo Nimbus

Ouf, je viens de le résoudre en Java. Mon "plus petit nombre avec 2 ^ n diviseurs" a fini par être représenté par une carte des nombres premiers et de leurs pouvoirs.

Ce casse-tête concerne autant l'optimisation que tout autre chose: faites travailler votre code, puis remettez-le à jour. Il vaut vraiment la peine de tester environ 2 ^ 30 diviseurs, car il est possible d'insérer toutes sortes de bogues de second ordre lors de l'optimisation. Réutilisez les résultats de calculs antérieurs, essayez de ne rien trier et arrêtez d'itérer dès que vous le pouvez (NavigableSet et LinkedHashMap étaient utiles). Je ne vais pas apprendre à ma grand-mère à tester efficacement la primalité.

J'ai longtemps utilisé Java, mais avec des nombres de cette taille, il est facile de passer par Long.MAX_VALUE au milieu d'un calcul, rappelez-vous: (A ^ 2 * B)% C = (A * ((A * B)% C ))% C.

J'espère que tout cela motive plutôt que de laisser tomber le jeu. Continue!

0
Adrian Redgers

Pas une réponse complète à la place quelques astuces:

  1. le diviseur entier max de n est n/2

    donc pas besoin de vérifier tous les diviseurs jusqu'à n

  2. peut utiliser une décomposition principale

    le diviseur principal maximal correspond à sqrt(n). Il n'est donc pas nécessaire de tester jusqu'à n, mais plutôt jusqu'à sqrt(n) ou jusqu'à un nombre égal à la moitié des n bits

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

    cela devrait accélérer un peu les choses, mais vous devez ajouter le calcul des diviseurs non principaux

  3. cela ressemble à une sorte de série (décomposition principale)

    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
    

    essayez donc de trouver l'équation qui génère cet ordre, puis calculez simplement l'itération n-th dans l'arithmétique modulo (opération PI simple ... modmul). Je peux voir que les éléments pairs et impairs ont une équation séparée ...

[edit1] décomposer jusqu'à 16 diviseurs

  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
0
Spektre

Comme l'explique Miljen Mikic, la fonction de dénombrement des diviseurs est déterminée par la factorisation en facteurs premiers. Pour calculer n, commencez à 1 et utilisez un algorithme glouton pour doubler le nombre de diviseurs k fois en choisissant le facteur le moins cher à chaque étape. Les coûts initiaux sont les nombres premiers, remplacés par leur carré lorsque vous les utilisez. Après avoir précalculé les k premiers nombres premiers, vous pouvez le faire rapidement avec un min-tas. En 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 __== "__main__":
    print(solve(500500, 500500507))
0
Colonel Panic

Le diviseur le plus élevé que tout nombre a, autre que lui-même, est la moitié du nombre. Par exemple, 120 a un diviseur maximal de 60 autre que lui-même. Ainsi, vous pouvez facilement réduire la plage de (n + 1) à (n/2). 

De plus, pour qu'un nombre ait m diviseurs, le nombre doit être au moins ((m-1) * 2) en suivant la logique ci-dessus (-1 car le m e nombre est lui-même). Par exemple, un nombre avec 4 diviseurs doit être au moins de 6. Ainsi, votre recherche pour n a une plage plus petite maintenant.

Ces deux réduiront un peu la durée d'exécution.

0
stolen_leaves