Нуждаюсь в помощи оптимизируя решение для Euler проблемы Проекта № 12

У меня была своя забава с Euler проблемами Проекта снова, и я заметил, что моим решением для номера 12 является один из моих самых медленных в ~593.275 ms на просмотр. Это является вторым к моему решению для номера 10 в ~1254.593 ms на просмотр. Все мои другие ответы берут меньше, чем 3 ms работать с наиболее хорошо под 1 ms.

Мое решение для Java для проблемы 12:

основной ():

int index = 1;
long currTriangleNum = 1;

while (numDivisors(currTriangleNum) <= 500) {
    index++;
    currTriangleNum += index;
}

System.out.println(currTriangleNum);

numDivisors ():

public static int numDivisors(long num) {  
    int numTotal = 0;

    if (num > 1)
        if (num % 2 == 0) {
            for (long i = 1; i * i <= num; i++)
                if (num % i == 0)
                    numTotal+=2;
        } else {
            // halves the time for odd numbers
            for (long i = 1; i * i <= num; i+=2)
                if (num % i == 0)
                    numTotal+=2;
    }
    else if (num == 0)
        return 0;
    else if (num == 1)
        return 1;
    else (num < 0)
        return numDivisors(num *= -1);

    return numTotal;
 }

.

При оглядывании форума решений некоторые люди нашли что эти формулы (n = (p^a) (q^b) (r^c)... И d (n) = (a+1) (b+1) (c+1)...), работал на них, но я лично не вижу, как это было бы немного быстрее; быстрее вручную, возможно, но не в программе.

.

Основной мыслительный процесс следующие:

Мы хотим вычислить количество делителей в 48. Путем рассмотрения факторного дерева ниже, мы можем завершить это 48 = (2^4)(3^1) [n = (p^a) (q^b) (r^c)...].

  48
 /  \
2   24
   /  \
  2   12
     /  \
    2   06
       /  \
      2    3 

Зная это, мы создаем формулу d(48) = (4+1)(1+1) [d (n) = (a+1) (b+1) (c+1)...], чтобы решить, что 48 имеет 10 факторов.

d(n)  = (a+1)(b+1)(c+1)...
d(48) = (4+1)(1+1)
d(48) = (5)(2)
d(48) = 10

.

Как я могу оптимизировать свой код? Действительно ли эти формулы являются лучшим решением? Я чувствую, что нахождение всех простых множителей, затем реализация формул заняли бы больше времени, чем программа, которую я уже имею в распоряжении.

Большое спасибо,

Justian

Править:

Прежде чем любой начнет отправлять ссылки, я осмотрел подобные вопросы в ТАК без любой удачи - я просто не могу продумать реализации их методов, которые работали бы более быстрый, чем, что я уже имею в распоряжении.

EDIT2:

Моя 2-я попытка Решета Эратосфена (для проблемы 10):

int p = 3, n = 2000000;
long total = 0;
boolean[] sieve = new boolean[n];

for (int i = 3; i < n; i += 2)
    sieve[i] = true;

sieve[2] = true;

while (p * p < n) {
    for (int i = p; i < n; i++)
        if (sieve[i] && (i % p) == 0)
            sieve[i] = false;
    p++;

    while (!sieve[p])
        p++;
}

for (int i = 0; i < n; i++)
    if (sieve[i])
        total += i;

System.out.println(total);

Выполнения в ~985.399 ms - не слишком много быстрее, чем другой метод, но еще не был оптимизирован. Это работает, как бы то ни было.

10
задан iCodez 22 January 2015 в 20:35
поделиться

3 ответа

Используйте базовую математическую структуру , это резко изменит время выполнения вашей программы. Кстати, это относится и к проблеме 10; если вы не можете сделать это за несколько миллисекунд, вы использовали крайне неэффективный алгоритм. Фактически, я советую вам сначала поработать над проблемой 10, потому что проблема 12 строится на ней.

Я собираюсь дать лучший алгоритм для задачи 12 ниже, но сначала сделаю наблюдение, которое должно значительно ускорить вашу программу. Если два числа x и y взаимно просты (т.е. у них нет общего делителя, кроме 1), то d (x · y) = d (x) · d (y). В частности, для треугольного числа d (n · (n + 1)) = d (n) · d (n + 1). Поэтому вместо перебора чисел треугольника n · (n + 1) переберите n: это значительно уменьшит размер аргументов, передаваемых в d (n).

Если вы выполните эту оптимизацию, вы заметите, что вы вычисляете d (n) дважды подряд (один раз как d ((n-1) +1) и один раз как d (n)). Это говорит о том, что кеширование результата d - хорошая идея.Приведенный ниже алгоритм делает это, но также вычисляет d снизу вверх, а не сверху вниз, что более эффективно, потому что умножение происходит намного быстрее, чем факторизация.


Проблема 10 может быть решена простым применением сита Эратосфена . Заполните массив логических значений (т.е. битовый вектор) размером 2000000 таким образом, чтобы с помощью sieve [i] == true if i было простым; затем просуммируйте числа, для которых sieve [i] == true .

Проблема 12 может быть решена путем обобщения решета Эратосфена. Вместо того, чтобы делать сито [i] логическим значением, указывающим, является ли i простым, сделайте его числом, указывающим количество способов, в которых оно не простое , то есть количество делителей i . Для этого легко изменить базовое сито Эратосфена: вместо установки сита [x * y] на false добавьте к нему 1.

Несколько последующих задач проекта Эйлера выигрывают от подобного подхода.

Одна из проблем, которые могут возникнуть у вас, заключается в том, что в задаче 12 неясно, когда прекращать вычисление сита. Вы можете пойти двумя путями:
1. вычисление сита по частям по запросу, само по себе стоящее упражнение в программировании (для этого потребуется более сложный код, чем второй метод)
2. Или начните с переоценки границы: найдите какое-нибудь треугольное число, которое имеет более 500 делителей, и вы знаете, что остановитесь перед этим числом или на нем.

Вы можете выиграть больше времени, если поймете, что вам нужно заботиться только о нечетных числах, поскольку d (2 ^ k · n) = (k + 1) · d (n), если n нечетное, и найти k и n задано только (2 ^ k · n) быстро на двоичном компьютере. Я оставлю детали этой оптимизации в качестве упражнения.

10
ответ дан 4 December 2019 в 01:29
поделиться

Думали ли вы о разбиении на простые множители и отслеживании простых чисел, чтобы вам не приходилось их пересчитывать?

0
ответ дан 4 December 2019 в 01:29
поделиться

Я сделал это некоторое время назад, поэтому я не помню всех оптимизаций, вот некоторые:

  1. используйте формулу суммирования для суммы (1 ... n)
  2. найти простые множители с помощью методов описано в проблеме 7 и проблеме 10
  3. определить, сколько делителей n имеет на основе его разложения на простые множители *

* Рассматривайте это как вопрос вероятности, если вы не знаете «правила». Например, у вас есть четыре вкуса, которые вы можете добавить в свой кофе. Сколько у вас вариантов?

0
ответ дан 4 December 2019 в 01:29
поделиться
Другие вопросы по тегам:

Похожие вопросы: