让函数 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。
有更好的方法吗?
答案 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)
说明:能够计算从1
到b
的总和就足够了,然后我们可以进行两次单独的计算并减去从a
到{{1的总和}}。查找从b
到1
的除数函数的总和相当于从整数序列的在线百科全书计算sequence A006218。该序列相当于从b
到floor(n / d)
的所有整数的d
和[{1}}范围的总和。
现在 序列可以被认为是双曲线1
下的整数值点的数量。我们可以使用线n
周围双曲线的对称性,并使用xy=n
和x = 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
之前,您不会遇到麻烦。
如果n
和2**52
真的关闭,可能会有更有效的解决方案。
答案 1 :(得分:1)
因为期望的结果是一个范围内所有数字的除数总数,所以不需要计算范围内各个数的除数。相反,计算1是除数,2是除数等的次数。这是O(b)计算。
即加起来b-(a-1)
,b/2 - (a-1)/2
,b/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[]
,其中包含从a
到b
的每个数字的除数。只需总结一下即可得到最终答案。
如果输入较大,可以将其拆分并添加每个返回段的总和。
爪哇:
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)。除非你对它的机制感兴趣,否则基本上没有理由使用筛子。