【问题标题】:How to calculate smallest number with certain number of divisors?如何计算具有一定数量除数的最小数字?
【发布时间】:2020-09-09 21:30:19
【问题描述】:

来自Project Euler problem 500

120 的除数是 16。实际上 120 是有 16 个除数的最小数。

找到具有 2**500500 个除数的最小数。以 500500507 为模数给出答案。

计算 n 的除数很简单,例如。在 Python 中len([i for i in range(1,n+1) if n % i == 0])。这是 O(n)。

我尝试了蛮力搜索,发现 32 个除数的最小数字是 840,但对于上述问题来说太慢了。从不等式count_divisors(n) <= n来看,这个数字会很大。

你怎么看?有任何想法吗?如何计算一定除数的最小数?


编辑:我不认为这是重复的。这个问题更具体,它涉及特定类别的更多更大的数字。 other question 普遍询问。它的答案不适用于这个问题,它是一个不同的量级。

【问题讨论】:

  • [检查这个][1]。 [1]:stackoverflow.com/questions/8861994/…
  • 如何通过最小除数相加直接构造数字,到达16、32时停止... // 1: 1 = 1 // 2 : 1*2 = 2 // 3 : 1*2*2 = 4 // ...
  • MrockKK 链接中的第一个答案很有用,虽然很难理解。更好的来源可能是here:您可以直接从数字的素数分解中计算除数。由于您正在寻找正好 2 除数的幂,这告诉您答案中的每个主要因素都必须比 2 的幂小一个。
  • @EmmanuelJay 这将不起作用,因为对于除数 d0<d1 的数量,n=LCM(all divisors) 的值不仅增加了......例如对于 d=5 除数最低 n=16 和 @ 987654331@ 除数最低n=12 !!!
  • 有趣的事实:这个谜题要求答案以 500500507 为模(这样可以节省输入时间),但它的长度是 3078556 位。

标签: algorithm math


【解决方案1】:

你应该使用整数n的除数公式:

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

在哪里

n = p1a1 * p2a 2 *p3a3 *...*pkak

是每个整数通过其素数除数的幂的唯一表示。这是一个众所周知的公式,但如果有人想知道如何得到它,请注意dn 除当且仅当d 的形式为 p1x 1 * p2x2 *p3x3 *...*pkxk,其中每个xi 介于 0 和 ai 之间,因此有 ai + 1 种可能性来选择 xi 中的每一个。现在只需应用乘积规则即可获得所需的公式。

对于固定的d(n)(如您的情况),n 的最小值显然是通过仔细选择现有素数的幂或通过添加新素数来获得的。让我们来看看这个简单的例子,16:

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

这意味着您最多有 4 个不同的素数,因此:

x = 2a1 * 3a2 *5a3 * 7a4

其中 ai >= 0。现在的问题是 - 为了获得 x 的最小值,增加 2 的幂是否更好(即增加 a 1),还是使用 7(即采用 a4=1 而不是 a4=0)?嗯,检查起来很简单,2*3*5*7 > 23 * 3 * 5 = 120,这就是为什么在这种情况下 120 是答案。

如何推广这种方法?您应该创建最小堆来放置素数的幂,注意除数的数量达到指定值。如果是 16,此最小堆将包含数字 2、3、5、7、22、32、24 等。 为什么?因为 16 = 24,所以 (ai+1) 中的每一个都必须除以 16,即它必须是 2 的幂。每次添加新幂时,它应该将左侧(即变量 d(x))增加 2 的幂,因为您的最终目标是找到具有 2500500 除数的最小数字。使用第一个 k 素数初始化堆(在问题陈述中,k = 500500),并且在每一步中,当您从堆中弹出 px 时,p2x返回,结果乘以 pxd(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。

【讨论】:

  • 整洁!你解释得很好。这正是我用最小堆计算的方式。
  • 这只是较大的d(n) 问题的一半,我相信。另一半是选择素数的幂。
  • @Michael 这不是什么大问题。从一个装满素数的最小堆开始,例如埃拉托色尼筛。有一次,当你从最小堆中弹出一个素数的幂时,将该幂的平方推回堆。这是保持d(x) 2 的幂的唯一方法。
  • 嗯,虽然我明白了整体的想法,但我不太明白“如何推广这种方法”之后的部分。堆到底是如何初始化的,它是如何更新的,更新何时停止,为什么这个过程会产生正确的答案?!我想我需要再想一想。
  • @peter.petrov 在答案中添加了一个分步示例,希望现在清楚了。
【解决方案2】:

这是我的 Javascript 的高级要点 - 其中factorCount 表示除数:

  • 找出factorCount的主因子分解
  • 生成这些主要因素的每个独特组合
  • 对于每个组合,从原始素数因子数组中提取这些组合值,并将提取的值相乘后的值添加到该数组中。然后按降序对元素进行排序。
  • 对于上一步创建的每个数组,在计算 2^(b1-1)*3^(b2-1)5^(b3- 时检查哪个产生的最小数 1)...
  • 计算出的最小数是具有factorCount 除数的最小数

这是我的 JavaScript 函数的高级代码分解:

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

这是完整的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;
}

将上面的代码块粘贴到浏览器控制台,你可以自己测试一下:

> 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

当输入达到大约 1 亿时,我的算法开始大便……所以对于 Project Euler 问题 500,输入将是 2^500500(一个非常非常非常大的数字),您需要另一种方法.然而,这是一个很好的通用方法,可以让你走得很远。希望对您有所帮助。

请给 cmets 留下效率优化建议。我很想听听。

【讨论】:

  • 我无法按照您的代码执行的操作。但如果它只能完成原始任务的简化版本,它真的是问题的答案吗?
  • 这确实回答了这个问题:“如何计算具有一定数量除数的最小数?”多达 100,000,000 多个除数。引用的 Project Euler 问题要求一个大得离谱的数字(尝试输入 2^ 500500 输入你的计算器)。
  • @Teepeemm 我的代码不是“试图做”任何事情,而是在做! ;)
【解决方案3】:

除自身以外,任何数的最高除数是该数的一半。例如,120 的最大除数为 60,而不是它自己。因此,您可以轻松地将范围从 (n+1) 减小到 (n/2)。

此外,对于一个有 m 个除数的数,该数必须至少为 ((m-1) * 2) 遵循上述逻辑(-1,因为第 m 个数是它自己)。例如,一个有 4 个除数的数必须至少为 6。因此,您现在对 n 的搜索范围更小了。

这两个会稍微减少运行时间。

【讨论】:

  • 我在执行此操作时遇到了问题。您是否最终将空间从(n + 1)减少到n的某个因子?我认为这最终不会有帮助。
  • 我将范围从 n+1 减少到 n/2 因为任何数字都将 n/2 作为其最大除数。例如,44 的最大除数为 22,100 的最大除数为 50。对于奇数,它会更小。因此,当您只能检查到一半时,检查到 n+1 是没有意义的。我知道这并没有对算法进行太多改进,但是算法的一部分(计算除数的部分)现在必须执行它最初执行的迭代次数的 1/2。
  • 我明白这一点。但是您正在将 500 小时的运行时间减少到 250 小时的运行时间(以弥补一些数字)。如果您想解决问题,就必须从完全不同的方向来解决问题。
  • 其实你可以把它减少到n的(整数部分)平方根。如果 a * b = n 且 a = sqrt(n)。 注意方便的优化提示:不要检查 a
【解决方案4】:

正如 Miljen Mikic 所解释的,除数计数函数由质因式分解决定。要计算 n,从 1 开始并使用贪心算法将除数的数量加倍 k 次,在每一步选择最便宜的因子。初始成本是质数,当你使用它们时被它们的平方代替。在预先计算前 k 个素数后,您可以使用最小堆快速完成此操作。在 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))

【讨论】:

    【解决方案5】:

    唷,我刚刚用java解决了它。我的“具有 2^n 个除数的最小数”最终被表示为素数及其幂的映射。

    这个难题与优化一样重要:让您的代码正常工作,然后努力重构。绝对值得测试最多约 2^30 个除数,因为在优化时有各种二阶错误潜入的空间。重用之前计算的结果,尽量不要对任何东西进行排序,并尽快停止迭代(NavigableSet 和 LinkedHashMap 很有用)。我不会教我的祖母有效地测试素数。

    我自始至终都使用 java long,但是对于这种大小的数字,在计算过程中很容易超过 Long.MAX_VALUE,请记住:(A^2 * B) % C = (A * ((A * B ) % C)) % C.

    希望所有这些都能激发而不是放弃比赛。继续!

    【讨论】:

      【解决方案6】:

      不完整回答一些提示:

      1. n 的最大整数除数是n/2

        所以不需要检查直到n的所有除数

      2. 可以使用素数分解

        最大素数除数为sqrt(n),因此无需测试到n,而是测试到sqrt(n) 或最多n 位的一半

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

        这应该会加快速度,但您需要添加非素数除数的计算

      3. 这看起来像是某种系列(素数分解)

        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
        

        所以尝试找到生成这个顺序的方程,然后简单地计算模运算中的n-th 迭代(简单的 PI 运算 ...modmul)。我可以看到偶数和奇数元素有单独的方程......

      [edit1] 分解到 16 个除数

        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
      

      【讨论】:

      • “所以试着找出产生这个顺序的方程”。这就是重点。我认为唯一节省时间的方法是使用除数公式。
      • @Teepeemm 是的,我同意...检查答案是正确的(它是在我写这篇文章时添加的)
      • 轻微修正:n 的最大整数除数确实是 n/2,但最大 prime 整数除数是 sqrt(n) [ 当然你应该检查 p*p
      • @AdrianRedgers 是的,答案也说明了这一点(项目符号 #2)但是我经常使用一半的位而不是 sqrt(n)p*p 测试 ...
      【解决方案7】:

      这是我的代码。您可以使用 Sieve 生成​​素数。如果您对我的代码有任何建议,请提出建议。

      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)
      

      【讨论】:

      • 欢迎来到 SO。你能解释一下你的代码吗?我发现很难跟上。更好的变量名也可能有所帮助——list1list2inumset1mainlistpossibles 都有些模糊。 getcombinations 似乎是一个列表或一个数字,两者都与通常的组合概念无关(例如docs.python.org/3.6/library/…)。这运行速度够快吗? findfactors让我很担心,但我说不出来。
      【解决方案8】:

      质因数分解 (PF),缩写形式,用于具有以下特征的最小整数 2**500,500 除数如下所示:

      2**31 * (3...7)**15 * (11...47)**7 * (53...2,713)**3 * (2,719...7,370,029)
      

      具有 2^n 个除数的最小正整数是前 n 个素数和素数幂的序列的乘积。素数的幂 (pp) 是素数的 2^(2^e) 幂(幂是 2,4,8,16...)。前 3 个 pp 是 4,9 和 16(2^2,3^2 和 2^4)。

      由于在 PF 中仅使用质因数,您会注意到 pp 与质因数聚合在一起。对于 n = 500,550,这里有一些统计数据或计数: PF 中的 5 个术语(31、15、7、3、1 次方)。 总质数 (P) = 500,084 素数的幂 (pp) = 416 总 P + pp = n = 500,500 见上述定理。

      Single primes (the last term of the PF) total 499,688.
      

      您可以使用 Sage Math 和 Excel 计算这个 PF(以及其他 2^n)(尽管使用 Excel,您需要一个素数计数函数)。

      另外,您应该检查您的 PF 解决方案。这可以通过为各种 pp 和单个素数分配权重来检查原始 n 来完成。

      【讨论】:

        【解决方案9】:

        功能齐全的代码。

        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)
        

        【讨论】:

        • 如果您注册 Project Euler 并阅读常见问题解答 [projecteuler.net/about],在您登录后看起来有所不同,它解释了为什么您不应该通过发布问题的答案来破坏人们的学习之旅。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2023-03-10
        • 1970-01-01
        • 2021-12-01
        • 2022-06-10
        • 1970-01-01
        • 2018-06-23
        • 2013-02-15
        相关资源
        最近更新 更多