Сумма числа делителя числа между 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