Сумма числа делителя числа между a и b включительно
пусть будет функция 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 может стоить мне много времени....например - >если a=1 и b=2^31-1.
есть ли лучший способ, чтобы сделать?
5 ответов
вот простой, но достаточно эффективный код 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
to b
, тогда мы можем сделать два отдельных вычисления и вычесть, чтобы получить сумму из a
to b
. Нахождение суммы функции делителя из 1
to b
количество вычислений последовательность A006218 из онлайн-энциклопедии целочисленных последовательностей. Эта последовательность эквивалентна в сумме floor(n / d)
as d
диапазоны по всем целым числам от 1
to n
.
и теперь это последовательность можно рассматривать как число целочисленных точек под гиперболой xy=n
. Мы можем использовать симметрию гиперболы вокруг линии x = y
, и подсчитайте целочисленные точки с помощью x <= sqrt(n)
и с y <= sqrt(n)
. Это заканчивается двойным подсчетом очков с обоими x
и y
меньше sqrt(n)
, поэтому мы вычитаем площади floor(sqrt(n))
компенсировать. Все это объясняется (кратко) во введении к этой статье.
Примечания:
алгоритм имеет время выполнения
O(sqrt(b))
, и постоянн требования к космоса. Улучшения во времени выполнения возможны за счет пространства; см. документ, упомянутый выше.на самом деле большая
n
, вам понадобится правильный целочисленный квадратный корень, а не использованиеfloor(math.sqrt(n))
, чтобы избежать проблемы с неточностями с плавающей запятой. Это не проблема с такойn
то, на что вы смотрите. С типичной плавающей запятой IEEE 754 и правильно округленной операцией квадратного корня вы не столкнетесь с проблемами, покаn
превышает2**52
.если
a
иb
are действительно close, могут быть более эффективные решения.
поскольку желаемым результатом является общее количество делителей для всех чисел в диапазоне, нет необходимости подсчитывать делители отдельных чисел в диапазоне; вместо этого подсчитайте количество раз, когда 1 является делителем, 2-делителем и т. д. Это вычисление O(b).
то есть, добавлять b-(a-1)
, b/2 - (a-1)/2
, b/3 - (a-1)/3
, etc. .
в коде python, показанном ниже (который использует оператор python // для целочисленного деления с усечением), делители от 2 до около b / 2 подсчитываются с помощью for
петли. Обратите внимание, что делители, которые меньше 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
вы можете просеять количество делителей, а затем суммировать количество:
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
to 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 пример.
Edit: если вы ищете сито, это нормально. Однако, я должен сказать, что jwpat7 это!--22--> 1) быстрее, 2) постоянное пространство и 3) более элегантный (IMO). В принципе нет причин использовать сито, если вы не заинтересованы в его механике.
мы можем адаптировать этот алгоритм: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