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,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
займає близько 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,222 , а час, необхідний для тестування всіх значень на 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 на парні всі значенняa є дійсними.