C 投影欧拉问题#276-原始三角形

C 投影欧拉问题#276-原始三角形,c,performance,algorithm,math,C,Performance,Algorithm,Math,我试图解决这个问题,但到目前为止没有成功 考虑带整数的三角形 带有a的a、b和c侧≤ B≤ C一 整数边三角形(a、b、c)为 如果gcd(a,b,c)=1,则称为基元。怎么 许多基本整数边三角形 周长不超过 一万 我的代码中的瓶颈是GCD函数。对于我的示例输入,几乎需要90%的执行时间。我很想听到关于如何改进解决方案的提示和评论。。。我的解决方案是用C编写的,但很简单: #include <stdio.h> #include <math.h> #define side

我试图解决这个问题,但到目前为止没有成功

考虑带整数的三角形 带有a的a、b和c侧≤ B≤ C一 整数边三角形(a、b、c)为 如果gcd(a,b,c)=1,则称为基元。怎么 许多基本整数边三角形 周长不超过 一万

我的代码中的瓶颈是
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;
}
#包括
#包括
#定义边3
//这是我的程序花费大部分时间的地方
尺寸比gcd(尺寸a、尺寸b);
尺寸(尺寸a、尺寸b、尺寸c);
大小\u t计数\u基本\u三角形(大小\u t最大\u周长);
int main()
{
printf(“基本三角形=%lu\n”,
计数基本三角形(1000);
}
尺寸bi\U gcd(尺寸a、尺寸b)
{
尺寸t;
而(b!=0)
{
t=b;
b=a%b;
a=t;
}
返回a;
}
尺寸三号gcd(尺寸a、尺寸b、尺寸c)
{
返回BIU gcd(a,BIU gcd(b,c));
}
大小\u t计数\u基本\u三角形(大小\u t最大\u周长)
{
size\u t count=0;//基本三角形的数量
大小\u t a,b,c;//三角形的边
//以下是每边的边界
尺寸
//因为b>=a&&c>=b==>a的最大值
//当a+b+c>10000000时
//b==a(至少)
//c==b(至少)
//==>a+b+c>=10000000
//==>3*a>=10000000
//=>a=10000000/3
a_极限=最大周长/边数,
b_限制,
c_极限;
对于(a=1;a=b&&a+b+c2*b+a=10000000
//==>2*b=10000000-a
//==>b=(10000000-a)/2
对于(b=a,b_极限=(最大周长-a)/2;b c(三角形有效!)
//=>c
暴力解决方案往往非常缓慢:)

减少计算的提示:

  • 预先计算表中的所有素数 范围2..107并储存 将它们放在查找表中
  • bi\u gcd(a,b)
    检查
    a
    b
    是否为素数和 返回
    1
    否则计算 他们的gcd。
    tri_gcd(a、b、c)
    中执行相同操作
  • 编辑:考虑@interjay的评论:

    例如,如果
    a
    是素数,我们仍然必须检查
    b
    是否是
    a
    的倍数,

    类似这样的
    (b%a==0)
    。在这种情况下,
    a
    就是gcd。

    除了Nick D的答案,这将使你不再费心计算
    bi\u gcd
    ,当一个或两个输入都是素数时,我发现自己想知道你必须用相同(复合)数字调用
    bi\u gcd
    多少次。gcd(12,18)无论你计算多少次,结果总是会是6。我想,存储结果的机制可能会有所改进。

    以下是一些可以改进的地方:

    • 不要在内环中计算
      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作为一个小改进,您可以插入一些特殊情况,例如

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

      在优化之前进行评测是很好的,但是
      gcd
      函数中的大量时间并不一定意味着您需要(或可以)让它更快,而不是您太频繁地调用它。:-) 这里有一个提示-一个算法改进,将提高运行时间的数量级,而不仅仅是在实现上的改进

      您当前仅单独计算基本三角形。相反,问问自己:你能有效地计算周长为a+b+c=n的所有三角形(不一定是基本三角形)吗?(运行时间O(n)就可以了——您当前的算法是Ω(n3)。)完成后,哪些三角形的计数过多?例如,有多少个三角形用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。 此外,这些是整数并形成三角形(检查)
      • 所以周长为n的整数三角形(a,b,c)的数量 正是 n划分为具有相同奇偶性的部分(p,q,r)的分区数。您可能会发现这更容易计数
      另外/或者,尝试将该数字T(n)与较小的数字T(n-k)直接关联,以获得递归关系

      (当然,如果你在谷歌上搜索,也可以找到一些非常简单的公式,但这有什么意思?

      顺便说一句,这种技术被称为“记忆化”。5是素数,但gcd(5,10)不是1。您仍然需要检查