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"
Считая Шебанг одним байтом, добавлены новые строки для горизонтального рассудка.
Что-то вроде комбинации код-гольф, подача самого быстрого кода .
Кажется, что средняя (наихудшая?) Сложность случая составляет 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
Последние два из них, как представляется, являются наихудшими сценариями для других представлений. В обоих случаях показанное решение является буквально самой первой проверенной вещью. Ведь 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
занимает около 14 секунд на моем компьютере (71000 значений в секунду), а диапазон - 999000000..1000000000
около 20 секунд (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 , было увеличено с 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
При этом используются две предварительно вычисленные таблицы поправок: одна размером 10 КБ, другая - 253 КБ. Приведенный выше код включает функции генератора для этих таблиц, хотя, вероятно, их следует вычислять во время компиляции.
Версию с таблицами коррекции более скромного размера можно найти здесь: http://codepad.org/1ebJC2OV. Эта версия требует в среднем 1,620 итераций в семестр, наихудший случай - 38 , а весь диапазон работает примерно за 3 м 21 с. Немного времени компенсируется, используя побитовое and
для коррекции b , а не по модулю.
улучшения
Четные значения с большей вероятностью дают решение, чем нечетные.
В статье о ментальных вычислениях, связанной с ранее сделанными заметками, отмечается, что если после удаления всех четырех факторов значение, которое нужно разложить, является четным, это значение можно разделить на два, и решение можно восстановить:
Хотя это может иметь смысл для умственного вычисления (меньшие значения, как правило, легче вычислить), это не имеет большого смысла алгоритмически. Если вы возьмете 256 случайных 4- кортежей и изучите сумму квадратов по модулю 8 , вы обнаружите, что значения 1 , 3 , 5 и 7 достигаются в среднем по 32 раза. Однако значения 2 и 6 достигаются по 48 раз. Умножив нечетные значения на 2, вы найдете решение, в среднем, на 33% меньше итераций. Реконструкция заключается в следующем:
Необходимо позаботиться о том, чтобы a и b имели одинаковое соотношение, равно как и c и d , но если решение вообще было найдено, правильное упорядочение гарантировано существует.
Невозможные пути не нужно проверять.
После выбора второго значения b уже может оказаться невозможным существование решения, учитывая возможные квадратичные вычеты для любого заданного модуля. Вместо того, чтобы все равно проверять или переходить к следующей итерации, значение b можно «исправить», уменьшив его на наименьшее значение, которое может привести к решению. В двух таблицах поправок хранятся эти значения: одно для b , а другое для c . Использование более высокого модуля (точнее, использования модуля с относительно меньшим количеством квадратичных остатков) приведет к лучшему улучшению. Значение a не нуждается в коррекции; изменив n на четное, все значенияА действительны.