Algorithm 当平方和为N时,如何找到四个变量的所有可能值?

Algorithm 当平方和为N时,如何找到四个变量的所有可能值?,algorithm,language-agnostic,Algorithm,Language Agnostic,A^2+B^2+C^2+D^2=N给定一个整数N,打印出求解方程的所有可能的整数值组合 我猜我们可以比暴力做得更好。维基百科页面有一些有趣的背景信息,但拉格朗日的四平方定理(或者更准确地说,巴切定理——拉格朗日只证明了它)并没有真正详细说明如何找到上述平方 正如我在评论中所说,解决方案将是不平凡的。讨论了四平方和的可解性。该文件声称: 没有方便的算法(除了中提到的简单算法之外) (本文第二段)以寻求其他解决方案 通过计算表示来表示,但可能 这将通过提供什么类型的搜索的概念来简化搜索 解决方案存在

A^2+B^2+C^2+D^2=N
给定一个整数
N
,打印出求解方程的所有可能的整数值组合


我猜我们可以比暴力做得更好。

维基百科页面有一些有趣的背景信息,但拉格朗日的四平方定理(或者更准确地说,巴切定理——拉格朗日只证明了它)并没有真正详细说明如何找到上述平方

正如我在评论中所说,解决方案将是不平凡的。讨论了四平方和的可解性。该文件声称:

没有方便的算法(除了中提到的简单算法之外) (本文第二段)以寻求其他解决方案 通过计算表示来表示,但可能 这将通过提供什么类型的搜索的概念来简化搜索 解决方案存在,也不存在

还有一些与这个话题相关的有趣事实。那里 存在其他定理表明每个整数都可以写成一个整数 四个特定的平方倍数之和。例如,每个 整数可以写成N=a^2+2b^2+4c^2+14d^2。有54个 这样的情况对所有整数都适用,Ramanujan提供了 完整的名单是在1917年

有关详细信息,请参阅。这是不容易理解的,除非你有一些数论的背景。如果你能推广拉马努扬的54种形式,你可能会有一个更容易的时间。话虽如此,在我引用的第一篇文章中,有一个小片段讨论了一种可能找到所有解决方案的算法(尽管我觉得有点难以理解):

例如,1911年有报道称计算器Gottfried Ruckle被要求将N=15663减少为四个平方的和。他 在8秒内产生125^2+6^2+1^2+1^2的溶液,然后 立即通过125^2+5^2+3^2+2^2。更难的问题 (由远离原始数字的第一项反映, 用了56秒:11399=105^2 + 15^2 + 8^2 + 5^2. 通常,策略是首先将第一项设置为N以下的最大平方,并尝试表示 三个平方和的较小余数。那么第一学期是 设置为N下方的下一个最大正方形,以此类推。随着时间的推移a lightning calculator将熟悉表示小的 将数字表示为平方和,这将加快该过程

(我的重点。)


该算法被描述为递归算法,但它可以很容易地迭代实现。

似乎所有整数都可以通过这样的组合来实现:

0 = 0^2 + 0^2 + 0^2 + 0^2
1 = 1^2 + 0^2 + 0^2 + 0^2
2 = 1^2 + 1^2 + 0^2 + 0^2
3 = 1^2 + 1^2 + 1^2 + 0^2
4 = 2^2 + 0^2 + 0^2 + 0^2, 1^2 + 1^2 + 1^2 + 1^2 + 1^2
5 = 2^2 + 1^2 + 0^2 + 0^2
6 = 2^2 + 1^2 + 1^2 + 0^2
7 = 2^2 + 1^2 + 1^2 + 1^2
8 = 2^2 + 2^2 + 0^2 + 0^2
9 = 3^2 + 0^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 0^2
10 = 3^2 + 1^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 1^2
11 = 3^2 + 1^2 + 1^2 + 0^2
12 = 3^2 + 1^2 + 1^2 + 1^2, 2^2 + 2^2 + 2^2 + 0^2
.
.
.
诸如此类

当我在头脑中做一些初步的工作时,我认为只有完美的正方形才有超过1个可能的解。然而,在列出它们之后,我觉得它们似乎没有明显的顺序。然而,我想到了一种最适合这种情况的算法:

重要的是使用4元组(a,b,c,d)。在任何给定的4元组中,它是a^2+b^2+c^2+d^2=n的解,我们将为自己设置一个约束,即a始终是4中最大的,b是下一个,依此类推,如下所示:

a >= b >= c >= d
还要注意,a^2不能小于n/4,否则平方和必须小于n

那么算法是:

1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
2. For a in a range (min_a, max_a)
在这一点上,我们已经选择了一个特定的a,现在正在寻找从a^2到n-的差距,即(n-a^2)

等等等等。所以整个算法看起来像:

1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
2. For a in a range (min_a, max_a)
3a. Obtain floor(square_root(n - a^2))
3b. Obtain the first value of b such that b^2 >= (n - a^2)/3
4. For b in a range (min_b, max_b)
5a. Obtain floor(square_root(n - a^2 - b^2))
5b. Obtain the first value of b such that b^2 >= (n - a^2 - b^2)/2
6. For c in a range (min_c, max_c)
7. We now look at (n - a^2 - b^2 - c^2). If its square root is an integer, this is d.
Otherwise, this tuple will not form a solution
n = 3200724;
lim = sqrt (n) + 1;
for (a = 0; a <= lim; a++)
    for (b = 0; b <= lim; b++)
        for (c = 0; c <= lim; c++)
            for (d = 0; d <= lim; d++)
                if (a * a + b * b + c * c + d * d == n)
                    printf ("%d %d %d %d\n", a, b, c, d);
#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = 0, nb = na - a * a; b * b <= nb; b++) {
            for (c = 0, nc = nb - b * b; c * c <= nc; c++) {
                for (d = 0, nd = nc - c * c; d * d <= nd; d++) {
                    if (d * d == nd) {
                        printf ("%d %d %d %d\n", a, b, c, d);
                        count++;
                    }
                    tot++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}
在步骤3b和5b中,我使用(n-a^2)/3,(n-a^2-b^2)/2。我们分别除以3或2,因为元组中的值数量尚未“固定”

例如:

在n=12时执行此操作:

1a. max_a = 3
1b. min_a = 2
2. for a in range(2, 3):
    use a = 2
3a. we now look at (12 - 2^2) = 8
max_b = 2
3b. min_b = 2
4. b must be 2
5a. we now look at (12 - 2^2 - 2^2) = 4
max_c = 2
5b. min_c = 2
6. c must be 2
7. (n - a^2 - b^2 - c^2) = 0, hence d = 0
so a possible tuple is (2, 2, 2, 0)

2. use a = 3
3a. we now look at (12 - 3^2) = 3
max_b = 1
3b. min_b = 1
4. b must be 1
5a. we now look at (12 - 3^2 - 1^2) = 2
max_c = 1
5b. min_c = 1
6. c must be 1
7. (n - a^2 - b^2 - c^2) = 1, hence d = 1
so a possible tuple is (3, 1, 1, 1)

这是仅有的两个可能的元组-嘿,普雷斯托

天真的暴力会是这样的:

1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
2. For a in a range (min_a, max_a)
3a. Obtain floor(square_root(n - a^2))
3b. Obtain the first value of b such that b^2 >= (n - a^2)/3
4. For b in a range (min_b, max_b)
5a. Obtain floor(square_root(n - a^2 - b^2))
5b. Obtain the first value of b such that b^2 >= (n - a^2 - b^2)/2
6. For c in a range (min_c, max_c)
7. We now look at (n - a^2 - b^2 - c^2). If its square root is an integer, this is d.
Otherwise, this tuple will not form a solution
n = 3200724;
lim = sqrt (n) + 1;
for (a = 0; a <= lim; a++)
    for (b = 0; b <= lim; b++)
        for (c = 0; c <= lim; c++)
            for (d = 0; d <= lim; d++)
                if (a * a + b * b + c * c + d * d == n)
                    printf ("%d %d %d %d\n", a, b, c, d);
#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = 0, nb = na - a * a; b * b <= nb; b++) {
            for (c = 0, nc = nb - b * b; c * c <= nc; c++) {
                for (d = 0, nd = nc - c * c; d * d <= nd; d++) {
                    if (d * d == nd) {
                        printf ("%d %d %d %d\n", a, b, c, d);
                        count++;
                    }
                    tot++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}
对于
n=1000万
,在我杀死它之前已经持续了大约一个半小时

所以,我想说暴力在某种程度上是完全可以接受的。除此之外,还需要更多的数学解决方案


为了提高效率,您只能检查
d>=c>=b>=a
的解决方案。这是因为您可以将这些组合中的所有解决方案构建成排列(如果
a
b
c
d
中的两个或多个值相同,则可能会删除重复项)

此外,
d
循环的主体不需要检查
d
的每个值,只检查最后一个可能的值

在这种情况下,获取
1000000
的结果需要不到10秒的时间,而不是超过6分钟:

   0    0    0 1000
   0    0  280  960
   0    0  352  936
   0    0  600  800
   0   24  640  768
   :    :    :    :
 424  512  512  544
 428  460  500  596
 432  440  480  624
 436  476  532  548
 444  468  468  604
 448  464  520  560
 452  452  476  604
 452  484  484  572
 500  500  500  500
Found 1302 solutions

real   0m9.517s
user   0m9.505s
sys    0m0.012s
该守则如下:

#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = a, nb = na - a * a; b * b <= nb; b++) {
            for (c = b, nc = nb - b * b; c * c <= nc; c++) {
                for (d = c, nd = nc - c * c; d * d < nd; d++);
                if (d * d == nd) {
                    printf ("%4d %4d %4d %4d\n", a, b, c, d);
                    count++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}


(a) :所有计时都是通过注释掉内部的
printf
来完成的,这样I/O就不会扭曲图形。

nebffa有一个很好的答案。一项建议:

 step 3a:  max_b = min(a, floor(square_root(n - a^2))) // since b <= a
现在的问题是从数组中找出4个和(a,b,c,d)=N的数

我们可以将a从nr循环到nr/2,并计算r=N-S[a]; 现在的问题是从S中找出3个数,求和(b,c,d)=r=N-S[a]

以下是代码:

nr = square_root(N);
S = {0, 1, 2^2, 3^2, 4^2,.... nr^2};
for (a = nr; a >= nr/2; a--)
{
   r = N - S[a];
   // it is now a 3SUM problem
   for(b = a; b >= 0; b--)
   {
      r1 = r - S[b];
      if (r1 < 0) 
          continue;

      if (r1 > N/2) // because (a^2 + b^2) >= (c^2 + d^2)
          break;

      for (c = 0, d = b; c <= d;)
      {
          sum = S[c] + S[d];
          if (sum == r1) 
          {
               print a, b, c, d;
               c++; d--;
          }
          else if (sum < r1)
               c++;
          else
               d--;
      }
   }
}

请参阅Wikipedia和相关文章。这很可能需要一个动态规划解决方案或启发式。Wikipedia文章没有关于如何枚举四方问题的所有解决方案的信息(它主要讨论相关猜想的数学证明)。它确实引用了Rabin和Shallit的一篇文章,但是,这篇文章只讨论了如何找到一个单一的解决方案,而不是如何列举所有可能的解决方案。另外,那篇文章不是在线的,而是付费下载的。@rbaryyoung:这就是为什么它是一篇评论——只是为了获得关于4之和属性的更多信息
2. according to neffa's post (step 1a & 1b), a (which is the largest among all 4 numbers) is between [nr/2 .. nr]. 
nr = square_root(N);
S = {0, 1, 2^2, 3^2, 4^2,.... nr^2};
for (a = nr; a >= nr/2; a--)
{
   r = N - S[a];
   // it is now a 3SUM problem
   for(b = a; b >= 0; b--)
   {
      r1 = r - S[b];
      if (r1 < 0) 
          continue;

      if (r1 > N/2) // because (a^2 + b^2) >= (c^2 + d^2)
          break;

      for (c = 0, d = b; c <= d;)
      {
          sum = S[c] + S[d];
          if (sum == r1) 
          {
               print a, b, c, d;
               c++; d--;
          }
          else if (sum < r1)
               c++;
          else
               d--;
      }
   }
}
N            result#  time1     time2
-----------  -------- --------  -----------
  1,000,000   1302       859       281
 10,000,000   6262     16109      7938
100,000,000  30912    442469    344359