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"
シバングを1バイトとして数え、水平方向の健全性のために改行を追加しました。
コードとゴルフの 組み合わせが最速のコード送信。
平均的な(最悪?)ケースの複雑さはO(log n) O(n 0.07)のようです。実行速度が0.001秒より遅いことはありませんでした。900000000〜999999999の範囲全体をチェックしました。それよりもかなり長い時間(0.1秒以上)を要するものを見つけた場合は、お知らせください。
サンプルの使用法
$ 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
これらの最後の2つは、他の提出物の最悪のシーンのようです。どちらの場合でも、示されている解決策は、文字通り非常に最初にチェックされるものです。の場合123456789
、2番目です。
値の範囲をテストする場合は、次のスクリプトを使用できます。
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
私のコンピューターでは、範囲は約14 999000000..1000000000
秒(1秒あたり71000値)で、範囲は約20秒(1秒あたり50000値)で、O(log n)平均複雑度と一致しています。
更新
編集:このアルゴリズムは、少なくとも1世紀の間、電卓で使用されてきたものと非常に似ていることがわかりました。
最初に投稿してから、1..1000000000の範囲のすべての値をチェックしました。「最悪のケース」の動作は値699731569で示され、ソリューションに到達する前に合計190の組み合わせをテストしました。190を小さな定数であると見なす場合-そして私は確かにそうする-必要な範囲での最悪の場合の動作はO(1)と見なすことができます。つまり、巨大なテーブルからソリューションを検索するのと同じくらい高速で、平均してかなり高速です。
もう一つ。190回の反復後、144400を超えるものはすべて、最初のパスを超えていません。幅優先のトラバースのロジックは価値がありません-使用されていません。上記のコードはかなり短くすることができます:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
これは、検索の最初のパスのみを実行します。ただし、2番目のパスを必要とする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のすべての値をテストするのに必要な時間は18分 54秒から2分41秒に短縮されました。以前は最悪の場合、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
これは、2つの事前計算された補正テーブルを使用します。1つはサイズが10kb、もう1つは253kbです。上記のコードにはこれらのテーブルのジェネレーター関数が含まれていますが、これらはおそらくコンパイル時に計算される必要があります。
より控えめなサイズの補正テーブルを持つバージョンは、ここで見つけることができます:http://codepad.org/1ebJC2OVこのバージョンは、平均必要と1.620の最悪のケースでは、用語ごとに反復を38、約3メートル21Sで全範囲を実行。モジュロではなくビット単位and
のb補正を使用することで、少しの時間を補います。
改善点
偶数の値は、奇数の値よりも解を生成する可能性が高くなります。
以前にリンクされた暗算の記事では、4のすべての要因を削除した後、分解される値が偶数である場合、この値を2で割ることができ、解が再構築されることに注意してください。

これはメンタル計算には意味がありますが(値が小さいほど計算が容易になる傾向があります)、アルゴリズム的にはあまり意味がありません。256ランダム4を取る場合タプルを取り、8を法とする平方和を調べた、あなたは値がわかります1、3、5、および7は、それぞれの平均に達している32回。ただし、値2と6はそれぞれ48回に達します。奇数の値に2を掛けると、平均で33%少ない反復で解決策が見つかります。再構成は次のとおりです。

aとbが同じパリティを持ち、cとdも同じになるように注意する必要がありますが、解決策が見つかった場合は、適切な順序が存在することが保証されます。
不可能なパスをチェックする必要はありません。
2番目の値bを選択した後、与えられたモジュロの2次剰余を考えれば、解が存在することはすでに不可能かもしれません。とにかくチェックしたり、次の反復に移動したりする代わりに、bの値は、解につながる可能性のある最小量だけデクリメントすることで「修正」できます。2つの補正テーブルは、これらの値を保存します。1つはbで、もう1つはcです。より高いモジュロを使用すると(より正確には、2次剰余が比較的少ないモジュロを使用すると)、より良い改善が得られます。値aは修正する必要はありません。nを偶数に変更することにより、aは有効です。