假设存在一个函数 g(x)=x的约数个数。给定两个整数a和b,我们需要找到-
g(a)+g(a+1)....+g(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 之间迭代可能需要很长时间....例如,如果 a=1 并且 b=2^31-1。
有更好的方法吗?
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
到b
的总和。找到从1
到b
的约数函数之和相当于从在线整数序列百科全书中计算sequence A006218。该序列等同于在所有整数d
从1
到n
范围内变化时的floor(n / d)
的总和。该算法的运行时间为 O(sqrt(b))
,空间需求恒定。可以在牺牲空间的情况下提高运行时间;请参阅上述论文。
对于非常大的 n
,您需要一个正确的整数平方根而不是使用 floor(math.sqrt(n))
,以避免浮点精度问题。这不是您正在查看的那种 n
的问题。使用典型的 IEEE 754 浮点数和正确舍入的平方根操作,直到 n
超过 2**52
才会遇到问题。
如果 a
和 b
很接近,则可能有更有效的解决方案。
T
,A006218)的人来说也是完全可理解的。此外,代码简洁高效。干得好! - Bolodef 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
你可以筛选出每个数的因子数量,然后将它们相加:
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
这类似于埃拉托斯特尼筛法,但不是标记合数,而是对每个数字计算每个除数,包括质数和合数。如果b太大,您可以分段进行筛选。
{a...b} 的数字,因此这个方案也很容易处理分段。该函数返回一个 int [] ,其中包含从 a 到 b 每个数字的除数数量。只需将它们加起来即可得出最终答案。
如果您的输入较大,则可以将其拆分并添加每个返回段的总和。
Java:
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
表格以将其从内部循环中排除。这将加速它,并且如果您有足够的内存,则没有真正的缺点。
对于快速测试,这里是一个ideone.com示例。
编辑:如果您正在寻找筛法,那么这很好。但是,我必须说jwpat7的答案更快,常数空间,而且更优雅(在我看来)。除非您对其机制感兴趣,否则基本上没有使用筛法的理由。
我们可以通过将所有倍数加1而不是标记为“非质数”来适应这个算法:http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes。
如果a=1且b=n,那么时间复杂度将是o(n.ln(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
a
和b
对,还是只有一个? - Bernhard Barker