Проект Эйлера, 12 задача. Программа выводит пустой результат после того как число делителей превышает 90

Всем привет. Пытаюсь решить 12 задачу из Проекта Эйлера https://projecteuler.net/problem=12. Ожидается, что программа выведет triangle, как только количество count его делителей достигнет определенного значения. Написал такой код:

triangle = 0
c = 1
count = 0

while count < 91:
    count = 0
    triangle += c
    c += 1
    for i in range(1, triangle + 1):
        if triangle % i == 0:
            count += 1
    if count == 90:
        print(triangle)
        break

Пока count меньше 91 результат выводится корректно, когда count становится равным 91 или большему числу, то программа работает, но не выводит triangle. В чем заключается проблема?


Ответы (2 шт):

Автор решения: NIKITA TALANOV

Ваш код работает правильно, и проблема вероятнее всего в том, что количество вычислений очень велико, поэтому после 90 делителей ваша программа либо падает, либо решает очень долго. Попробуйте погуглить оптимальные решения задачи по нахождению количества делителей, они помогут вам сократить количество вычислений. Я предлагаю вам такую оптимизацию: не проверять делимость после triangle/2, так как известно, что x после x/2 натуральных делителей иметь не может. Так вы сокращаете количество вычислений вдвое. На 500 не пробовал, попробовал на 200, через две минуты решилось.

DIVIDERS_COUNT = 200 # количество делителей искомого числа

triangle = 0 #треугольные числа
c = 1 # переменная для натуральных чисел
count = 1 # счетчик количества делителей


while count < DIVIDERS_COUNT:
    count = 1 # счетчику сразу присваивается 1, так как мы не будем считать triangle
    triangle += c
    for i in range(1, int(triangle/2) + 1 ):
        if triangle % i == 0:
            count += 1
    c += 1
print(triangle)
→ Ссылка
Автор решения: Stanislav Volodarskiy

Проблема в условии while count < 91:. Треугольное число T224 = 25200 имеет ровно 90 делителей. Следующее треугольное число с большим количеством делителей – T384 = 73920. У него 112 делителей. Вообще количество делителей числа очень любит само состоять из большого количества маленьких множителей. Например 90 = 2·3·3·5, 112 = 2·2·2·2·7. А если вы захотите отыскать треугольное число с 91 множителем ровно, вам придётся ждать долго, возможно, бесконечно долго.

Если заменить условие на while True:, ваша программа отыщет T384 быстрее чем за секунду.

Для запуска программы с разными параметрами я её переделал:

def tau(n):
    c = 0
    for i in range(1, n + 1):
        if n % i == 0:
            c += 1
    return c


k = int(input())
i = 1
while True:
    t_i = i * (i + 1) // 2
    count = tau(t_i)
    if count >= k:
        print(i, t_i, count)
        break
    i += 1

Пользоваться так: на входе желаемое число делителей, на выходе индекс i, само Ti и количество делителей τ(Ti). Чтобы отыскать следующее число с большим количеством делителей, подайте на вход число τ(Ti) + 1. Получится цепочка вроде:

n 1 2 3 5 7 10 17 19 21 25 37
i 1 2 3 7 8 15 24 32 35 63 80
Ti 1 3 6 28 36 120 300 528 630 2016 3240
τ(Ti) 1 2 4 6 9 16 18 20 24 36 40
n 41 49 91 113 129 145 163 169 ...
i 104 224 384 560 935 1224 1664 1728 ...
Ti 5460 25200 73920 157080 437580 749700 1385280 1493856 ...
τ(Ti) 48 90 112 128 144 162 168 192 ...

Чем дальше, тем дольше ждать следующее число. В конце таблицы время подбирается к минуте на новое число. А двигаться далеко, нужно > 500, мы добрались до 192. Нужна оптимизация.

Процедура tau(n) вычисляет количество делителей числа n: τ(n). Подробности в статье Функция делителей. τ(n) мультипликативна. Треугольное число вычисляется как: Ti = i(i + 1)/2. Множители i и i + 1 взаимнопросты, а тогда:

  • если i = 2k, то k и 2k + 1 взаимнопросты и τ(Ti) = τ(k(2k + 1)) = τ(k)τ(2k + 1);
  • если i = 2k + 1, то 2k + 1 и k + 1 взаимнопросты и τ(Ti) = τ((2k + 1)(k + 1)) = τ(2k + 1)τ(k + 1).

Время работы tau(n) пропорционально n, следовательно такое разбиение может серъёзно сократить время поиска: одно n заменяется на два сомножителя порядка √n. В конце таблицы мы перевалили через миллион, можно ожидать ускорения в тысячу раз. Код:

def tau(n):
    c = 0
    for i in range(1, n + 1):
        if n % i == 0:
            c += 1
    return c


k = int(input())
i = 1
while True:
    f1 = i
    f2 = i + 1
    if f1 % 2 == 0:
        f1 //= 2
    else:
        f2 //= 2
    count = tau(f1) * tau(f2)
    if count >= k:
        print(i, f1 * f2, count)
        break
    i += 1

И продолжение таблицы:

n 193 241 321 481
i 2015 2079 5984 12375
Ti 2031120 2162160 17907120 76576500
τ(Ti) 240 320 480 576

Задача решена. Новая программа тратит на неё меньше семи секунд:

$ time echo 500 | python pe12.py
12375 76576500 576

real  0m6.361s
user  0m6.356s
sys   0m0.004s

P.S. Можно улучшить процедуру tau(n), перебирать множители только до √n это ещё ускорит поиск. Можно считать число делителей по разложению n на простые множители, и это тоже ускорит поиск. Я этого не делаю, довольно того что задача номер двеннадцать решена сравнительно просто и можно переходить к следующей. Их там много.

→ Ссылка