项目欧拉问题#276 - 原始三角形

时间:2010-02-14 11:53:12

标签: c performance algorithm math

我试图解决problem# 276 from Project Euler,但到目前为止没有成功。

  

考虑带整数的三角形   a,b和c侧,a≤b≤c。一个   整数边三角形(a,b,c)是   如果gcd(a,b,c)= 1,则称为primitive。怎么样   许多原始的整数边三角形   存在的周长不超过   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 个答案:

答案 0 :(得分:9)

在优化之前进行性能分析是件好事,但gcd函数中的大量时间并不一定意味着您需要(或可以)使其更快,而不是你是经常叫它。 :-) 这是一个提示 - 一种算法改进,可以将运行时间提高几个数量级,而不仅仅是实现方面的改进。

您目前正在单独计算原始三角形。相反,问问自己:你能否有效地计算所有三角形(不一定是原始的)周长a + b + c = n? (运行时间O(n)会这样做 - 你当前的算法是Ω(n 3 )。)完成后,你有哪些三角形过度计算?例如,有多少个三角形用p分开边? (提示:a + b + c = n&lt; =&gt;(a / p)+(b / p)+(c / p)= n / p。)和so on

编辑:在解决问题并检查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。 此外,这些是整数并形成三角形(检查)。
  • 所以周长为n的整数三角形(a,b,c)的数量 确切地说 n的分区数为相同奇偶校验的部分(p,q,r)。您 可能 发现这更容易计算。

此外/或者,尝试直接将此数字T(n)与较小的数字T(n-k)相关联,以获得递归关系。

(当然,如果你是Google,你也可以找到一些非常简单的公式,但有什么好玩的呢?)

答案 1 :(得分:4)

这些是你可以改进的:

  • 不是在内循环中计算tri_gcd(a,b,c),而是在第二个循环内计算g1 = gcd(a,b),在内循环中计算g2 = gcd(g1, c)

  • gcd(a,b)==1时,您可以避免内部循环并将计数增加max_c - min_c + 1,因为您知道对于任何c值,gcd将为1。

  • 你的内循环似乎太高了,因为它没有检查a+b+c <= 10000000

不幸的是,即使有这些变化以及到目前为止在其他答案中提到的变化,这可能会太慢。我相信真正的解决方案不会枚举所有可能的三角形,但不知何故将它们分组。

答案 2 :(得分:2)

暴力解决方案往往非常缓慢:)

提示减少计算:

  1. 预先准备好所有素数 范围2..10 7 并存储 他们在查询表中。
  2. bi_gcd(a, b) 检查ab是否为素数 返回1否则计算 他们的gcd。
    tri_gcd(a, b, c)
  3. 中执行相同的操作

    编辑:考虑@interjay的评论:

    如果例如a是素数,我们仍然需要检查ba的倍数, 像这样(b % a == 0)。在这种情况下,a是gcd。

答案 3 :(得分:0)

除了Nick D的回答,当一个或两个输入为素数时,这会阻止你在计算bi_gcd时遇到困难,我发现自己想知道你必须用调用bi_gcd多少次相同的(复合)数字。无论你计算多少次,GCD(12,18)总是为6。我怀疑,存储结果的机制可以改善事情。

答案 4 :(得分:0)

作为一项小改进,您可以插入一些特殊情况,例如

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)。