Perl- 116个字节 87个字节(请参阅下面的更新)
#!perl -p
$.<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$.}($j++)x4;$n&&redo}
$_="@a"
将shebang视为一个字节,添加了换行符以保持水平。
组合式代码高尔夫球 最快的代码提交方式。
平均(最糟的)案例复杂度似乎是O(log n) O(n 0.07)。我没有发现运行速度低于0.001s,并且检查了整个范围900000000-999999999。如果您发现需要更长的时间,大约〜0.1s或更长时间,请告诉我。
样品用量
$ echo 123456789 | timeit perl four-squares.pl
11110 157 6 2
Elapsed Time: 0:00:00.000
$ echo 1879048192 | timeit perl four-squares.pl
32768 16384 16384 16384
Elapsed Time: 0:00:00.000
$ echo 999950883 | timeit perl four-squares.pl
31621 251 15 4
Elapsed Time: 0:00:00.000
对于其他提交者,最后两个似乎是最坏的情况。在这两种情况下,显示的解决方案实际上都是检查的第一件事。对于123456789
,这是第二个。
如果要测试值的范围,可以使用以下脚本:
use Time::HiRes qw(time);
$t0 = time();
# enter a range, or comma separated list here
for (1..1000000) {
$t1 = time();
$initial = $_;
$j = 0; $i = 1;
$i<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$i}($j++)x4;$n&&redo}
printf("%d: @a, %f\n", $initial, time()-$t1)
}
printf('total time: %f', time()-$t0);
最好通过管道传输到文件。该范围1..1000000
在我的计算机上花费大约14s(每秒71000个值),并且999000000..1000000000
花费大约20s(每秒50000个值),与O(log n)平均复杂度一致。
更新资料
编辑:事实证明,该算法与心理计算器至少一个世纪以来一直使用的算法非常相似。
自从最初发布以来,我已经检查了1..1000000000范围内的每个值。值699731569显示了“最坏情况”的行为,该值在得出解决方案之前测试了总共190种组合。如果您认为190是一个很小的常数(我当然会这样做),则可以将所需范围内的最坏情况视为O(1)。也就是说,与从巨型表中查找解决方案一样快,并且平均而言,可能更快。
不过另一件事。经过190次迭代后,大于144400的任何值都没有超过第一次通过。广度优先遍历的逻辑毫无价值-甚至没有使用。上面的代码可以大大缩短:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
仅执行搜索的第一遍。我们确实需要确认144400以下没有任何值需要第二遍,但是:
for (1..144400) {
$initial = $_;
# reset defaults
$.=1;$j=undef;$==60;
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
# make sure the answer is correct
$t=0; $t+=$_*$_ for @a;
$t == $initial or die("answer for $initial invalid: @a");
}
简而言之,对于1..1000000000范围,存在一个接近恒定的时间解,您正在研究它。
更新更新
@Dennis和我对该算法做了一些改进。如果您有兴趣,可以按照下面的评论和后续讨论中的进度进行。所需范围的平均迭代次数已从刚刚超过4次降至1.229,测试1..1000000000所有值所需的时间已从18m 54s减少到2m 41s。最坏的情况以前需要190次迭代。现在最坏的情况是854382778,只需要21个。
最终的Python代码如下:
from math import sqrt
# the following two tables can, and should be pre-computed
qqr_144 = set([ 0, 1, 2, 4, 5, 8, 9, 10, 13,
16, 17, 18, 20, 25, 26, 29, 32, 34,
36, 37, 40, 41, 45, 49, 50, 52, 53,
56, 58, 61, 64, 65, 68, 72, 73, 74,
77, 80, 81, 82, 85, 88, 89, 90, 97,
98, 100, 101, 104, 106, 109, 112, 113, 116,
117, 121, 122, 125, 128, 130, 133, 136, 137])
# 10kb, should fit entirely in L1 cache
Db = []
for r in range(72):
S = bytearray(144)
for n in range(144):
c = r
while True:
v = n - c * c
if v%144 in qqr_144: break
if r - c >= 12: c = r; break
c -= 1
S[n] = r - c
Db.append(S)
qr_720 = set([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121,
144, 145, 160, 169, 180, 196, 225, 241, 244, 256, 265, 289,
304, 324, 340, 361, 369, 385, 400, 409, 436, 441, 481, 484,
496, 505, 529, 544, 576, 580, 585, 601, 625, 640, 649, 676])
# 253kb, just barely fits in L2 of most modern processors
Dc = []
for r in range(360):
S = bytearray(720)
for n in range(720):
c = r
while True:
v = n - c * c
if v%720 in qr_720: break
if r - c >= 48: c = r; break
c -= 1
S[n] = r - c
Dc.append(S)
def four_squares(n):
k = 1
while not n&3:
n >>= 2; k <<= 1
odd = n&1
n <<= odd
a = int(sqrt(n))
n -= a * a
while True:
b = int(sqrt(n))
b -= Db[b%72][n%144]
v = n - b * b
c = int(sqrt(v))
c -= Dc[c%360][v%720]
if c >= 0:
v -= c * c
d = int(sqrt(v))
if v == d * d: break
n += (a<<1) - 1
a -= 1
if odd:
if (a^b)&1:
if (a^c)&1:
b, c, d = d, b, c
else:
b, c = c, b
a, b, c, d = (a+b)>>1, (a-b)>>1, (c+d)>>1, (c-d)>>1
a *= k; b *= k; c *= k; d *= k
return a, b, c, d
它使用两个预先计算的校正表,一个校正表,大小为10kb,另一个校正表为253kb。上面的代码包括这些表的生成器函数,尽管这些函数可能应该在编译时进行计算。
可以在以下位置找到具有更适中的校正表的版本:http : //codepad.org/1ebJC2OV该版本每个术语平均需要进行1.620次迭代,最坏的情况是38次,并且整个范围的运行时间约为3m 21s。通过按位and
进行b校正而不是取模,可以节省一点时间。
改进之处
偶数比奇数更有可能产生解决方案。
链接到先前的心理计算文章指出,如果在除去所有四个因子之后,要分解的值是偶数,则可以将该值除以2,然后重新构造解:

尽管这对于心理计算可能有意义(较小的值往往更易于计算),但在算法上并没有太大意义。如果拿256随机4元组,并检查平方和模8,你会发现的值1,3,5,和7分别平均达到32倍。但是,值2和6分别达到48次。将奇数值乘以2可以平均减少33%的迭代次数。重建如下:

需要注意的是,a和b以及c和d都具有相同的奇偶性,但是,如果根本找不到解决方案,则可以保证存在适当的排序。
不需要的路径不需要检查。
在选择第二个值b之后,考虑到任何给定模的可能二次残差,解决方案可能已经不存在。可以通过将b的值递减可能导致求解的最小量来“校正” b,而不是进行任何检查或进入下一个迭代。两个校正表存储这些值,一个用于b,另一个用于c。使用较高的模(更准确地说,使用具有相对较少的二次残基的模)将导致更好的改进。值a不需要任何校正;通过将n修改为偶数,一个有效。