A^2+B^2+C^2+D^2 = N
给定一个整数N
,打印出ABCD
整数值的所有可能组合,以解决这个问题。
我猜我们可以比蛮力更好。
答案 0 :(得分:7)
天真的蛮力就像是:
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 >= c >= b >= a
处的解决方案。那是因为您可以将这些组合中的所有解决方案构建为排列(可能重复删除a
,b
,c
或{{1}中的两个或更多个值是完全相同的。)
此外,d
循环的正文不需要检查d
的每个值,只是最后一个值。
在这种情况下获取d
的结果需要十秒钟而不是超过六分钟:
1,000,000
该代码如下:
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
而且,根据DSM的建议,#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;
}
循环可以完全消失(因为只有d
的一个可能值(折扣负数)并且可以计算出来,这带来了对我来说,一百万箱减少到两秒钟,一千万箱减少了68秒。
该版本如下:
d
(a):所有时间都是在内部#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;
}
注释掉的情况下完成的,这样I / O就不会对数字产生偏差。
答案 1 :(得分:4)
维基百科页面有一些有趣的背景信息,但拉格朗日的四方定理(或更正确地说,巴切特定理 - 拉格朗日只证明了它)并没有真正详细说明如何找到所述方格。
正如我在评论中所说,解决方案将是非常重要的。 This paper讨论了四平方和的可解性。该文件声称:
没有方便的算法(除了提到的简单算法之外) 本文第二段)寻找其他解决方案 表示的计算表明,但也许 这将通过给出什么样的概念来简化搜索 解决方案有并且不存在。
还有一些与此主题相关的其他有趣事实。那里 存在其他定理,表明每个整数都可以写成a 四个特定倍数的平方和。例如,每一个 整数可写为N = a ^ 2 + 2b ^ 2 + 4c ^ 2 + 14d ^ 2。有54个 像这样的情况对于所有整数都是正确的,Ramanujan提供了 1917年的完整清单。
有关详细信息,请参阅Modular Forms。除非你有数论背景,否则这不容易理解。如果你可以概括Ramanujan的54个表格,你可能会有更轻松的时间。话虽如此,在我引用的第一篇论文中,有一个小片段讨论了可以找到每个解决方案的算法(即使我发现它有点难以理解):
例如,据报道,计算器Gottfried在1911年 要求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 闪电计算器会变得熟悉表达小 数字作为平方和,这将加快过程。
(强调我的。)
该算法被描述为递归,但它可以很容易地迭代实现。
答案 2 :(得分:2)
好像所有整数都可以通过这样的组合制作:
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元组中,它是^ 2 + b ^ 2 + c ^ 2 + d ^ 2 = n的解,我们将自己设置一个约束,即a始终是4中的最大值,b是下一个,并且等等:
a >= b >= c >= d
另请注意,^ 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,现在正在考虑弥合从^ 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)
这是唯一两个可能的元组 - 嘿presto!
答案 3 :(得分:0)
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)).
现在的问题是从数组中找到4个数字,总和(a,b,c,d)= N;
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个数和和(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)
。
以下是在我的VM上运行java的测试结果(以毫秒为单位的时间,结果#是有效组合的总数,带打印输出的时间1,没有打印输出的time2):
N result# time1 time2
----------- -------- -------- -----------
1,000,000 1302 859 281
10,000,000 6262 16109 7938
100,000,000 30912 442469 344359