Задача Эйлера проекта № 276 - Примитивные треугольники - PullRequest
3 голосов
/ 14 февраля 2010

Я пытался решить проблему # 276 из Project Euler , но пока безуспешно.

Рассмотрим треугольники с целым числом стороны a, b и c с a ≤ b ≤ c. Целочисленный треугольник (a, b, c) называется примитивным, если gcd (a, b, c) = 1. Как много примитивных треугольников с целыми сторонами существуют с периметром, не превышающим 10 000 000?

Узким местом в моем коде является функция GCD. Это почти занимает 90% времени выполнения для моего образца ввода. Я хотел бы услышать подсказки, комментарии о том, как улучшить решение ... Мое решение написано на C, но оно довольно простое:

#include <stdio.h>
#include <math.h>

#define sides 3
// This is where my program spends most of its time
size_t bi_gcd (size_t a, size_t b);
size_t tri_gcd (size_t a, size_t b, size_t c);
size_t count_primitive_triangles (size_t maximum_perimeter);

int main()
{
 printf( "primitive_triangles = %lu \n",
            count_primitive_triangles(1000) );
}

size_t bi_gcd (size_t a, size_t b)
{
 size_t t;

 while(b != 0)
 {
  t = b;
  b = a % b;
  a = t;
 }

 return a;
}

size_t tri_gcd (size_t a, size_t b, size_t c)
{
 return bi_gcd(a, bi_gcd(b, c));
}

size_t count_primitive_triangles (size_t max_perimeter)
{
 size_t count = 0; // number of primitive triangles
 size_t a, b, c;   // sides of the triangle
 // the following are the bounds of each side
 size_t 
  // because b >= a && c >= b ==> max of a
  // is when a+b+c > 10,000,000
  // b == a (at least)
  // c == b (at least)
  // ==> a+b+c >= 10,000,000
  // ==> 3*a   >= 10,000,000
  // ==> a= 10,000,000/3
  a_limit = max_perimeter/sides,
  b_limit,
  c_limit;

 for (a = 1; a <= a_limit; ++a)
 {
  // because c >= b && a+b+c <= 10,000,000
  // ==> 2*b + a = 10,000,000
  // ==> 2*b = 10,000,000 - a
  // ==> b = (10,000,000 - a)/2
  for (b = a, b_limit = (max_perimeter-a)/2; b <= b_limit; ++b)
  {
   // The triangle inequality:
   // a+b > c (for a triangle to be valid!)
   // ==> c < a+b
   for (c = b, c_limit = a+b; c < c_limit; ++c)
   {
    if (tri_gcd(a, b, c) == 1)
     ++count;
   }
  }
 }

 return count;
}

Ответы [ 5 ]

9 голосов
/ 14 февраля 2010

Хорошо, что вы профилируете перед оптимизацией, но тот факт, что много времени в функции gcd не обязательно означает, что вам нужно (или можно) сделать это быстрее, а не вызывать его слишком часто. :-) Вот подсказка - алгоритмическое улучшение, которое улучшит время выполнения на порядки, а не просто улучшение в реализации.

В настоящее время вы считаете только примитивные треугольники по отдельности. Вместо этого задайте себе вопрос: можете ли вы эффективно подсчитать все треугольники (необязательно примитивные) с периметром a + b + c = n? (Время выполнения O (n) подойдет - ваш текущий алгоритм равен Ω (n 3 ).) Сделав это, какие треугольники вы переоценили? Например, сколько существует треугольников с p, разделяющим стороны? (Подсказка: a + b + c = n <=> (a / p) + (b / p) + (c / p) = n / p.) И и т. Д. .

Редактировать : После решения проблемы и проверки потока в Project Euler я обнаружил, что есть несколько других хороших подходов к этой проблеме, но вышеупомянутый подход является наиболее распространенным, и он работает. Для первой части вы можете посчитать это напрямую (некоторые люди сделали это; это, безусловно, выполнимо), или вы можете посчитать эту дополнительную подсказку полезной:

  • Предположим, что 1 ≤ a ≤ b ≤ c и a + b + c = n. Пусть p = b + c-a = n-2a, q = c + a-b = n-2b и r = a + b-c = n-2c. Тогда 1 ≤ r ≤ q ≤ p и p + q + r = a + b + c = n. Кроме того, p, q, r имеют одинаковую четность с n.
  • И наоборот, для любого 1 ≤ r ≤ q ≤ p с p + q + r = n со всеми тремя, имеющими одинаковую четность (как n), пусть a = (q + r) / 2, пусть b = (r + p) / 2, c = (p + q) / 2. Тогда 1 ≤ a ≤ b ≤ c и a + b + c = n. Кроме того, они являются целыми числами и образуют треугольник (отметка).
  • Итак, число целых треугольников (a, b, c) с периметром n это точно количество разбиений n на части (p, q, r) одинаковой четности. Вы можете посчитать это проще для подсчета.

Дополнительно / альтернативно, попробуйте напрямую связать это число T (n) с меньшими числами T (n-k), чтобы получить рекуррентное соотношение.

(Конечно, есть и несколько очень простых формул, которые вы можете найти, если вы Google, но что тут интересного?)

4 голосов
/ 14 февраля 2010

Вот некоторые вещи, которые вы можете улучшить:

  • Вместо вычисления tri_gcd(a,b,c) во внутреннем цикле вычислите g1 = gcd(a,b) во втором цикле и g2 = gcd(g1, c) во внутреннем цикле.

  • Когда gcd(a,b)==1, вы можете избежать внутреннего цикла и увеличить счет на max_c - min_c + 1, так как вы знаете, что gcd будет 1 для любого значения c.

  • Ваш внутренний цикл кажется слишком большим, поскольку он не проверяет, что a+b+c <= 10000000.

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

2 голосов
/ 14 февраля 2010

Решения грубой силы, как правило, очень медленные:)

Подсказка для сокращения вычислений:

  1. Пересчитайте все простые числа в диапазон 2..10 7 и магазин их в таблице поиска.
  2. В bi_gcd(a, b) проверьте, является ли a или b простым и return 1 иначе рассчитать их gcd.
    Сделайте то же самое в tri_gcd(a, b, c).

Редактировать: с учетом комментария @ interjay:

Если, например, a - простое число, мы все равно должны проверить, является ли b кратным a,
как то так (b % a == 0). В этом случае a является gcd.

0 голосов
/ 14 февраля 2010

В качестве небольшого улучшения можно добавить некоторые особые случаи, например,

size_t bi_gcd (size_t a, size_t b) {
    if (a == 1 || b == 1) return 1;
    ...

size_t tri_gcd (size_t a, size_t b, size_t c) {
    if (a%2==0 && b%2==0 && c%2==0) return 2;
    // of course GCD(a,b,c) can be > 2,
    // but the exact GCD is not needed in this problem.
    ...

С этими двумя if -s я могу сократить время, затрачиваемое с 1,2 до 1,0 секунды (с gcc -O3).

0 голосов
/ 14 февраля 2010

В дополнение к ответу Ника D, который остановит вас от необходимости вычислять bi_gcd, когда один или оба ввода просты, я задаюсь вопросом, сколько раз вы должны вызывать bi_gcd с одинаково ) цифры . GCD (12, 18) всегда будет равен 6, независимо от того, сколько раз вы его вычислите. Я подозреваю, что механизм хранения результатов может улучшить ситуацию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...