a和b之间的数的除数的总和

时间:2013-10-15 12:47:01

标签: algorithm numbers

让函数 g(x)= x的除数。给定两个整数a和b,我们需要找到 - >

G(A)+ G(A + 1)... +克(b)所示。

我认为这一步 - >

for every x from a to b

sum+=number of divisor of x(in sqrt(x) complexity)

但其给定 1< = a< = b< = 2 ^ 31-1

因此,在a和b之间进行迭代可能会花费我很多时间....对于eg->如果a = 1且b = 2 ^ 31-1。

有更好的方法吗?

5 个答案:

答案 0 :(得分:4)

这是一些简单但效率相当高的Python代码,可以完成这项任务。

import math

def T(n):
    "Return sum_{i=1}^n d(i), where d(i) is the number of divisors of i."
    f = int(math.floor(math.sqrt(n)))
    return 2 * sum(n // x for x in range(1, f+1)) - f**2

def count_divisors(a, b):
    "Return sum_{i=a}^b d(i), where d(i) is the number of divisors of i."
    return T(b) - T(a-1)

说明:能够计算从1b的总和就足够了,然后我们可以进行两次单独的计算并减去从a到{{1的总和}}。查找从b1的除数函数的总和相当于从整数序列的在线百科全书计算sequence A006218。该序列相当于从bfloor(n / d)的所有整数的d和[{1}}范围的总和。

现在 序列可以被认为是双曲线1下的整数值点的数量。我们可以使用线n周围双曲线的对称性,并使用xy=nx = y计算整数点。这最终会使x <= sqrt(n)y <= sqrt(n)小于x的点重复计算,因此我们减去y的平方以进行补偿。所有这些都在this paper的简介(简要)中进行了解释。

说明:

  • 算法具有运行时间sqrt(n)和恒定空间要求。可以牺牲空间来改善运行时间;见上面提到的论文。

  • 对于非常大的floor(sqrt(n)),您需要一个正确的整数平方根,而不是使用O(sqrt(b)),以避免浮点不准确的问题。你所看到的那种n并不是问题。使用典型的IEEE 754浮点和正确舍入的平方根操作,在floor(math.sqrt(n))超过n之前,您不会遇到麻烦。

  • 如果n2**52 真的关闭,可能会有更有效的解决方案。

答案 1 :(得分:1)

因为期望的结果是一个范围内所有数字的除数总数,所以不需要计算范围内各个数的除数。相反,计算1是除数,2是除数等的次数。这是O(b)计算。

即加起来b-(a-1)b/2 - (a-1)/2b/3 - (a-1)/3等。

在下面显示的python代码中(使用python运算符//进行截断的整数除法)使用for循环计算从2到大约b / 2的除数。请注意,小于b但大于max(a, b/2)的除数每次出现一次,不需要在循环中计数。代码使用表达式b-max(a,(b+1)//2+1)+1来计算它们。程序后显示输出。

当要处理k个不同的a,b集合时,可以计算时间O(k + bₘₐₓ)中的所有答案,其中bₘₐₓ是最大值b

Python代码:

def countdivisors(a,b):
    mid = (b+1)//2+1
    count = b-a+1 +b-max(a,mid)+1 # Count for d=1 & d=n
    for d in xrange(2,mid):
        count += b//d - (a-1)//d
    return count
# Test it:
a=7
for b in range(a,a+16):
    print '{:3} {:3} : {:5}'.format(a, b, countdivisors(a,b))

输出:

  7   7 :     2
  7   8 :     6
  7   9 :     9
  7  10 :    13
  7  11 :    15
  7  12 :    21
  7  13 :    23
  7  14 :    27
  7  15 :    31
  7  16 :    36
  7  17 :    38
  7  18 :    44
  7  19 :    46
  7  20 :    52
  7  21 :    56
  7  22 :    60

答案 2 :(得分:0)

我们可以调整此算法:http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes 通过向所有倍数添加1而不是将它们标记为“非素数”

它将是o(n.ln(n)),a = 1且b = n(我认为)

算法1到n:

g: array of n elements
for i starting with 2 to n
    if g[i]== 0
        for each multiple of i <n
            g[i] += 1  

答案 3 :(得分:0)

你可以筛选除数的数量,然后计算总数:

function divCount(a,b)
    num := makeArray(1..b, 0)
    for i from 1 to b
        for j from i to b step i
            num[j] := num[j] + 1
    sum := 0
    for i from a to b
        sum := sum + num[i]
    return sum

这类似于Eratosthenes的筛子,但它不是标记复合物,而是计算每个数字的每个除数,包括素数和复合物。如果 b 太大,您可以分段执行筛选。

答案 4 :(得分:0)

另一种基于筛选的答案,但比其他人的时间复杂度更高。这个也很容易处理分段,因为它只在每次运行时筛选数字{a...b}。该函数返回int[],其中包含从ab的每个数字的除数。只需总结一下即可得到最终答案。

如果输入较大,可以将其拆分并添加每个返回段的总和。

爪哇:

public static int[] getDivisorCount(int a, int b){
    int[] sieve = new int[b - a + 1];
    double max = Math.ceil(Math.sqrt(b));
    for(int i = 1; i <= max; i++){
        int j = (a / i) * i;
        if(j < a)
            j += i;
        for( ; j <= b; j += i){
            double root = Math.sqrt(j);
            if(i < root){
                sieve[j - a] += 2;
            }else if(i == root){
                sieve[j - a]++;
            }
        }
    }
    return sieve;
}

外循环运行sqrt(b)次。内循环运行类似log(b-a)次,所以除非我弄错了,最后的复杂性应该是O(sqrt(b) * log(b)),因为最坏的情况是a=1。随意纠正我。

如果您正在处理大型输入并且有足够的空间,则可能需要考虑预先填充sqrt表以使其脱离内部循环。它会加速它,如果你有足够的记忆力,那就没有真正的缺点。

快速测试here's an ideone.com example

编辑:如果您正在寻找筛子,这很好。但是,我必须说jwpat7's answer是1)更快,2)恒定空间,3)更优雅(IMO)。除非你对它的机制感兴趣,否则基本上没有理由使用筛子。