У меня была своя забава с 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; если вы не можете сделать это за несколько миллисекунд, вы использовали крайне неэффективный алгоритм. Фактически, я советую вам сначала поработать над проблемой 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) быстро на двоичном компьютере. Я оставлю детали этой оптимизации в качестве упражнения.
Думали ли вы о разбиении на простые множители и отслеживании простых чисел, чтобы вам не приходилось их пересчитывать?
Я сделал это некоторое время назад, поэтому я не помню всех оптимизаций, вот некоторые:
* Рассматривайте это как вопрос вероятности, если вы не знаете «правила». Например, у вас есть четыре вкуса, которые вы можете добавить в свой кофе. Сколько у вас вариантов?