【问题标题】:How to find all possible values of four variables when squared sum to N?当平方和为N时如何找到四个变量的所有可能值?
【发布时间】:2012-08-01 00:06:41
【问题描述】:

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

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

【问题讨论】:

  • 参见维基百科上的this article,以及相关文章。
  • 这很可能需要动态编程解决方案或启发式方法。
  • 维基百科的文章没有关于如何枚举四平方问题的所有解的信息(它主要谈论相关猜想的数学证明)。它确实引用了 Rabin 和 Shallit 的一篇文章,但是,该文章仅讨论了如何找到单个解决方案,而不是如何枚举所有可能的解决方案。此外,该文章不在线,而是付费下载。
  • @RBarryYoung:这就是为什么它是一个评论 - 只是为了更多关于 4 个平方和的属性的信息。
  • 跟进:我在 CS 论坛上发帖,这里 (cs.stackexchange.com/questions/2988/…)。似乎理论上最快可以做到这一点是 O(N*Log(Log(N))),而我在那里发布的 c# 方法似乎确实实现了这一点。

标签: algorithm language-agnostic


【解决方案1】:

天真的蛮力是这样的:

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;
}

它仍然是蛮力,但没有那么粗暴,因为它知道何时尽早停止每个级别的循环。

在我(相对)适度的盒子上,不到一秒钟 (a) 即可获得最多 50,000 个数字的所有解决方案。除此之外,它开始需要更多时间:

     n          time taken
----------      ----------
   100,000            3.7s
 1,000,000       6m, 18.7s

对于n = ten million,在我杀死它之前已经过了大约一个半小时。

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


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

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

在这种情况下,获取 1,000,000 的结果需要不到 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;
}

而且,根据 DSM 的建议,d 循环可以完全消失(因为只有一个可能的值 d(扣除负数)并且可以计算出来),从而减少了一百万个案例对我来说是 2 秒,1000 万个案例是更易于管理的 68 秒。

那个版本如下:

#include <stdio.h>
#include <math.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++) {
                nd = nc - c * c;
                d = sqrt (nd);
                if (d * d == nd) {
                    printf ("%d %d %d %d\n", a, b, c, d);
                    count++;
                }
            }
        }
    }

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

    return 0;
}

(a):所有的计时都是在注释掉内部printf 的情况下完成的,这样I/O 就不会扭曲数字。

【讨论】:

  • 您可以进一步提高蛮力:不需要d 上的内部循环(毕竟只有一个d 可以工作),并且您可以对条款强加命令(说增加)所以你只需要考虑每个多重集一次而不是每个排列。 [当然,您必须检查哪些项等于添加正确的数字。] 这使 10^6 的情况对我来说降低到 0.9 秒,而 10^7 的情况降低到 28 秒。
  • 好主意,@DSM,我在之前的编辑中加入了其中一个,但得到d 的计算是真正的改进。
  • 我发现我需要检查d>=c,否则我得到一些重复的解决方案。我是用 Java 编码的,所以我的代码可能和你的不一样。
【解决方案2】:

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

正如我在评论中所说,解决方案将很重要。 This paper 讨论四平方和的可解性。该论文声称:

没有方便的算法(除了中提到的简单算法 本文的第二段)用于寻找额外的解决方案 表示的计算表明,但也许 这将通过给出什么样的想法来简化搜索 解决方案存在和不存在。

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

有关详细信息,请参阅Modular Forms。除非你有一些数论背景,否则这并不容易理解。如果您可以概括 Ramanujan 的 54 种形式,您可能会更轻松。话虽如此,在我引用的第一篇论文中,有一个小的 sn-p 讨论了一种可能找到所有解决方案的算法(尽管我觉得它有点难以理解):

例如,据报道,在 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 的下一个最大正方形,依此类推。 随着时间的推移 闪电计算器会熟悉表达小 数字作为平方和,这将加快这个过程。

(强调我的。)

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

【讨论】:

  • +1:很好的研究。最后一句如果是第二个引用,实际上也强调了如何应用动态规划来增强朴素算法。
  • 条件必须是largest square below N还是largest square less than or equals N?如果是largest square below N,那么我们如何处理完美的正方形?
【解决方案3】:

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

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)

3. Repeat steps 1a through 2 to select a value of b. This time instead of finding
floor(square_root(n)) we find floor(square_root(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

在步骤 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)

这是仅有的两个可能的元组 - 嘿,快!

【讨论】:

  • 四平方问题总有解。
  • 这是真的——但是其他人都知道,这个问题在被问到之后就已经被编辑过了,当它被问到时,它现在已经删除了“interview-questions”标签。在一次采访中,无法使用谷歌来确定这一点,因此我在我的解决方案中包含了一些工作步骤,这些步骤使我使用直觉得出了这样的假设。毕竟这是面试要测试的。
【解决方案4】:

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

 step 3a:  max_b = min(a, floor(square_root(n - a^2))) // since b <= a

max_c 和 max_d 也可以用同样的方法改进。

这是另一个尝试:

1. generate array S: {0, 1, 2^2, 3^2,.... nr^2} where nr = floor(square_root(N)). 

现在的问题是从数组中找出 sum(a, b,c,d) = N; 的 4 个数;

2. according to neffa's post (step 1a & 1b), a (which is the largest among all 4 numbers) is between [nr/2 .. nr]. 

我们可以将 a 从 nr 循环到 nr/2 并计算 r = N - S[a]; 现在的问题是从 S 中找出 3 个数 sum(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--;
      }
   }
}

运行时是 O(sqare_root(N)^3)

这是在我的虚拟机上运行 java 的测试结果(时间以毫秒为单位,结果#是有效组合的总数,时间 1 有打印输出,时间 2 没有打印输出):

N            result#  time1     time2
-----------  -------- --------  -----------
  1,000,000   1302       859       281
 10,000,000   6262     16109      7938
100,000,000  30912    442469    344359

【讨论】:

    猜你喜欢
    • 2021-01-06
    • 1970-01-01
    • 1970-01-01
    • 2012-02-05
    • 2021-02-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多