Чотири квадрата разом


19

Четверта теорема Лагранжа говорить про те, що будь-яке натуральне число може бути представлене як сума чотирьох квадратних чисел. Ваше завдання - написати програму, яка це робить.

Вхід: Натуральне число (менше 1 млрд.)

Вихід: чотири числа, квадрати яких відповідають цьому номеру (порядок не має значення)

Примітка: Вам не доведеться здійснювати грубу обшуку! Деталі тут і тут . Якщо є функція, яка реалізує цю проблему (я визначу), вона заборонена. Дозволяються автоматизовані функції простих розмірів і квадратний корінь. Якщо є більше одного представництва, будь-яке - це добре. Якщо ви вирішили зробити грубу силу, вона повинна працювати протягом розумного часу (3 хвилини)

зразок введення

123456789

вихід вибірки (або добре)

10601 3328 2 0
10601 3328 2

Я можу зробити грубу силу, хоча якщо мій код скоротить?
Мартін Ендер

@ m.buettner Так, але він повинен працювати з великою кількістю
qwr

@ m.buettner Прочитайте публікацію, будь-яке природне число, що не перевищує 1 мільярда
qwr

Ах вибачте, що це не помітив
Мартін Ендер

2
@Dennis Натуральні числа в цьому випадку не включають 0.
qwr

Відповіді:


1

CJam, 50 байт

li:NmF0=~2/#:J(_{;)__*N\-[{_mqJ/iJ*__*@\-}3*])}g+p

Моя третя (і остаточна, обіцяю) відповідь. Цей підхід в значній мірі заснований на відповіді примо .

Спробуйте його в Інтернеті в інтерпретаторі CJam .

Використання

$ cjam 4squares.cjam <<< 999999999
[189 31617 567 90]

Фон

  1. Побачивши оновлений алгоритм primo, я повинен був побачити, як оцінюватиметься реалізація CJam:

    li{W):W;:N4md!}g;Nmqi)_{;(__*N\-[{_mqi__*@\-}3*])}g+2W#f*p
    

    Всього 58 байт! Цей алгоритм працює майже в постійний час і не виявляє великих варіацій для різних значень N. Давайте змінимо це ...

  2. Замість того, щоб починати з floor(sqrt(N))та зменшувати, ми можемо починати в 1та збільшувати. Це економить 4 байти.

    li{W):W;:N4md!}g;0_{;)__*N\-[{_mqi__*@\-}3*])}g+2W#f*p
    
  3. Замість того, щоб виразити Nяк 4**a * b, ми можемо виразити це як p**(2a) * b- де pнайменший основний фактор N- для збереження ще 1 байта.

    li_mF0=~2/#:J_*/:N!_{;)__*N\-[{_mqi__*@\-}3*])}g+Jf*p
    
  4. Попередня модифікація дозволяє злегка змінити реалізацію (у цілому сам алгоритм): Замість поділу Nна p**(2a)і помножити на вирішенні, p**aми можемо безпосередньо обмежити можливі рішення кратних p**a. Це економить ще 2 байти.

    li:NmF0=~2/#:J!_{;J+__*N\-[{_mqJ/iJ*__*@\-}3*])}g+`
    
  5. Не обмежуючи перше ціле число кратними, p**aзберігає додатковий байт.

    li:NmF0=~2/#:J(_{;)__*N\-[{_mqJ/iJ*__*@\-}3*])}g+`
    

Заключний алгоритм

  1. Знайдіть aі bтаке, що N = p**(2a) * bтам, де bне кратне p**2і pє найменшим основним фактором N.

  2. Встановити j = p**a.

  3. Встановити k = floor(sqrt(N - j**2) / A) * A.

  4. Встановити l = floor(sqrt(N - j**2 - k**2) / A) * A.

  5. Встановити m = floor(sqrt(N - j**2 - k**2 - l**2) / A) * A.

  6. Якщо N - j**2 - k**2 - l**2 - m**2 > 0, встановіть j = j + 1і поверніться до кроку 3.

Це можна здійснити так:

li:N          " Read an integer from STDIN and save it in “N”.                        ";
mF            " Push the factorization of “N”. Result: [ [ p1 a1 ] ... [ pn an ] ]    ";
0=~           " Push “p1” and “a1”. “p1” is the smallest prime divisor of “N”.        ";
2/#:J         " Compute p1**(a1/2) and save the result “J”.                           ";
(_            " Undo the first two instructions of the loop.                          ";
{             "                                                                       ";
  ;)_         " Pop and discard. Increment “J” and duplicate.                         ";
  _*N\-       " Compute N - J**2.                                                     ";
  [{          "                                                                       ";
    _mqJ/iJ*  " Compute K = floor(sqrt(N - J**2)/J)*J.                                ";
    __*@      " Duplicate, square and rotate. Result: K   K**2   N - J**2             ";
    \-        " Swap and subtract. Result: K   N - J**2 - K**2                        ";
  }3*]        " Do the above three times and collect in an array.                     ";
  )           " Pop the array. Result: N - J**2 - K**2 - L**2 - M**2                  ";
}g            " If the result is zero, break the loop.                                ";
+p            " Unshift “J” in [ K L M ] and print a string representation.           ";

Орієнтири

На моїх Intel Core i7-3770 я запустив усі 5 версій у всіх додатних цілих чисел до 999,999,999, виміряв час виконання та підрахував ітерації, необхідні для пошуку рішення.

Наступна таблиця показує середню кількість ітерацій та часу виконання для одного цілого числа:

Version               |    1    |    2    |    3    |    4    |    5
----------------------+---------+---------+---------+---------+---------
Number of iterations  |  4.005  |  28.31  |  27.25  |  27.25  |  41.80
Execution time [µs]   |  6.586  |  39.69  |  55.10  |  63.99  |  88.81
  1. Всього 4 ітерації та 6,6 мікро секунди на ціле число, алгоритм primo неймовірно швидкий.

  2. Почати floor(sqrt(N))починається з більшого сенсу, оскільки це дає нам менші значення для суми решти трьох квадратів. Як і очікувалося, починати з 1 набагато повільніше.

  3. Це класичний приклад погано реалізованої гарної ідеї. Для того, щоб фактично зменшити розмір коду, ми покладаємось на це mF, що розбиває ціле число N. Хоча версія 3 вимагає менших ітерацій, ніж версія 2, на практиці це набагато повільніше.

  4. Хоча алгоритм не змінюється, версія 4 набагато повільніше. Це відбувається тому, що він виконує додатковий поділ з плаваючою комою та ціле множення у кожній ітерації.

  5. Для введення N = p**(2a) ** bалгоритму 5 потрібні (k - 1) * p**a + 1ітерації, де потрібна kкількість ітерацій, алгоритм 4 яких потрібен. Якщо k = 1або a = 0, це не має ніякого значення.

    Однак будь-яке введення форми 4**a * (4**c * (8 * d + 7) + 1)може працювати досить погано. Для початкового значення j = p**a, N - 4**a = 4**(a + c) * (8 * d + 7)тому воно не може бути виражене сумою трьох квадратів. Таким чином, k > 1і хоча б p**aітерації потрібні.

    На щастя, оригінальний алгоритм primo неймовірно швидкий і N < 1,000,000,000. Найгірший випадок, який я міг би знайти вручну - 265,289,728 = 4**10 * (4**1 * (7 * 8 + 7) + 1)це 645 ітерацій. Час виконання на моїй машині менше 300 мс. У середньому ця версія в 13,5 разів повільніше, ніж реалізація алгоритму приму.


"Замість вираження Nяк 4**a * b, ми можемо виразити це як p**(2a) * b". Це насправді поліпшення . Мені б хотілося, щоб це було включено, але це було набагато довше (ідеал - знайти найбільший ідеальний квадратний коефіцієнт). "Починаючи з 1 і збільшуючи, економить 4 байти." Це, безумовно, повільніше. Час виконання для будь-якого заданого діапазону в 4-5 разів довше. "Усі натуральні числа до 999,999,999 займали 24,67 годин, даючи середній час виконання 0,0888 мілісекунд на ціле число." Перлу знадобилося лише 2,5 години, щоб розчавити весь діапазон, а переклад Python на 10 разів швидший;)
прим

@primo: Так, ти маєш рацію. Поділ на p**a- це вдосконалення, але воно невелике. Поділ на найбільший ідеальний квадратний коефіцієнт має велику різницю, починаючи з 1; це все ще поліпшення, починаючи з цілої частини квадратного кореня. Реалізація цього коштувала б лише двох байт. Час бездоганного виконання, мабуть, пояснюється моїми невдосконаленнями, а не CJam. Я повторно проведу тести для всіх алгоритмів (включаючи запропонований вами алгоритм), рахуючи ітерації, а не вимірювання часу на стіні. Подивимося, скільки часу це займе ...
Денніс

Знаходження найбільшого квадратного коефіцієнта коштує лише 2 додаткових байти ?! Що це за чаклунство?
примо

@primo: Якщо ціле число знаходиться на стеці, 1\поміняє його на 1 (акумулятор), mFпідштовхує його факторизацію і {~2/#*}/піднімає кожен простий коефіцієнт до його показника, поділеного на два, а потім помножує його на акумулятор. Для прямої реалізації вашого алгоритму додається лише 2 байти. Невелика відмінність полягає в основному з - за незручною , як я повинен був знайти експоненту 4, так як CJam ні (здається,) мають той час як петля ...
Dennis

У будь-якому випадку, еталон закінчено. Загальна кількість ітерацій, необхідних для розподілу всіх 1 000 000 цілих чисел, не знаходячи найбільшого квадратного коефіцієнта, становить 4 004 829 417, час виконання 1,83 години. Поділ на найбільший квадратний коефіцієнт зменшує кількість ітерацій до 3,996,724,799, але збільшує час до 6,7 годин. Схоже, факторизація займає набагато більше часу, ніж пошук квадратів ...
Денніс,

7

ФРАКТРАН: 156 98 фракцій

Оскільки це класична проблема теорії чисел, який кращий спосіб вирішити це питання, ніж використовувати числа!

37789/221 905293/11063 1961/533 2279/481 57293/16211 2279/611 53/559 1961/403 53/299 13/53 1/13 6557/262727 6059/284321 67/4307 67/4661 6059/3599 59/83 1/59 14279/871933 131/9701 102037079/8633 14017/673819 7729/10057 128886839/8989 13493/757301 7729/11303 89/131 1/89 31133/2603 542249/19043 2483/22879 561731/20413 2483/23701 581213/20687 2483/24523 587707/21509 2483/24797 137/191 1/137 6215941/579 6730777/965 7232447/1351 7947497/2123 193/227 31373/193 23533/37327 5401639/458 229/233 21449/229 55973/24823 55973/25787 6705901/52961 7145447/55973 251/269 24119/251 72217/27913 283/73903 281/283 293/281 293/28997 293/271 9320827/58307 9831643/75301 293/313 28213/293 103459/32651 347/104807 347/88631 337/347 349/337 349/33919 349/317 12566447/68753 13307053/107143 349/367 33197/349 135199/38419 389/137497 389/119113 389/100729 383/389 397/383 397/39911 397/373 1203/140141 2005/142523 2807/123467 4411/104411 802/94883 397/401 193/397 1227/47477 2045/47959 2863/50851 4499/53743 241/409 1/241 1/239

Приймає вхід форми 2 n × 193 і виводить 3 a × 5 b × 7 c × 11 d . Можна запустити за 3 хвилини, якщо у вас справді хороший перекладач. Можливо.

... добре, не дуже. Це виглядало такою цікавою проблемою у FRACTRAN, що мені довелося спробувати. Очевидно, це не належне рішення питання, оскільки це не вимагає часу (це жорстокі сили), і ледве навіть гольф, але я подумав, що я опублікую це тут, тому що це не кожен день, що питання Codegolf можна зробити у FRACTRAN;)

Підказка

Код еквівалентний наступному псевдо-Python:

a, b, c, d = 0, 0, 0, 0

def square(n):
    # Returns n**2

def compare(a, b):
    # Returns (0, 0) if a==b, (1, 0) if a<b, (0, 1) if a>b

def foursquare(a, b, c, d):
    # Returns square(a) + square(b) + square(c) + square(d)

while compare(foursquare(a, b, c, d), n) != (0, 0):
    d += 1

    if compare(c, d) == (1, 0):
        c += 1
        d = 0

    if compare(b, c) == (1, 0):
        b += 1
        c = 0
        d = 0

    if compare(a, b) == (1, 0):
        a += 1
        b = 0
        c = 0
        d = 0

7

Математика 61 66 51

Показані три методи. Тільки перший підхід відповідає вимозі часу.


1-FindInstance (51 char)

Це повертає єдине рішення рівняння.

FindInstance[a^2 + b^2 + c^2 + d^2 == #, {a, b, c, d}, Integers] &

Приклади та терміни

FindInstance[a^2 + b^2 + c^2 + d^2 == 123456789, {a, b, c, d}, Integers] // AbsoluteTiming

{0.003584, {{a -> 2600, b -> 378, c -> 10468, d -> 2641}}}

FindInstance[a^2 + b^2 + c^2 + d^2 == #, {a, b, c, d}, Integers] &[805306368]

{0.004437, {{a -> 16384, b -> 16384, c -> 16384, d -> 0}}}


2-IntegerPartitions

Це також працює, але занадто повільно, щоб відповідати вимозі швидкості.

f@n_ := Sqrt@IntegerPartitions[n, {4}, Range[0, Floor@Sqrt@n]^2, 1][[1]]

Range[0, Floor@Sqrt@n]^2- це набір усіх квадратів менше квадратного кореня n(найбільший можливий квадрат у розділі).

{4}вимагає цілих розділів, що nскладаються з 4 елементів із згаданого набору квадратів.

1, в межах функції IntegerPartitionsповертає перше рішення.

[[1]]знімає зовнішні брекети; рішення було повернуто у вигляді набору одного елемента.


f[123456]

{348, 44, 20, 4}


3-PowerRepresentations

PowerRepresentations повертає всі рішення задачі на 4 квадрата. Він також може вирішувати суми інших повноважень.

PowersRepresentations за 5 секунд повертає 181 спосіб виразити 123456789 як суму 4 квадратів:

n= 123456;
PowersRepresentations[n, 4, 2] //AbsoluteTiming

солі

Однак для інших сум це занадто повільно.


Нічого собі, Mathematica швидко виконує грубу силу. Чи IntegerPartitions робить щось набагато розумніше, ніж пробувати кожну комбінацію, як, наприклад, конверсія DFT на множинах? Технічні характеристики запитують числа, до речі, не їхні квадрати.
xnor

Я думаю, що Mathematica використовує грубу силу, але, ймовірно, оптимізував IntegerPartitions. Як видно з таймінгів, швидкість сильно змінюється залежно від того, чи є перше (найбільше) число близьким до квадратного кореня n. Дякуємо, що виявили порушення специфікації в попередній версії.
DavidC

Не могли б ви орієнтуватися f[805306368]? Не ділячись на потужності 4 спочатку, для мого рішення потрібно 0,05 с за 999999999; Я відмінив орієнтир на 805306368 через 5 хвилин ...
Денніс

f[805306368]повертається {16384, 16384, 16384}через 21 хвилину. Я використовував {3} замість {4}. Спроба розв’язати її сумою 4 ненульових квадратів була невдалою після декількох годин бігу.
DavidC

Я не маю доступу до Mathematica, але з того, що я прочитав у центрі документації, IntegerPartitions[n,4,Range[Floor@Sqrt@n]^2має працювати також. Однак я не вважаю, що ви повинні використовувати метод 1 для своєї оцінки, оскільки він не відповідає часовому обмеженню, вказаному у питанні.
Денніс

7

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 є дійсними.


1
Це неймовірно! Остаточний алгоритм, мабуть, найпростіший з усіх відповідей, але 190 ітерацій - це все, що потрібно ...
Денніс,

@Dennis Я був би дуже здивований, якби це не було зазначено в іншому місці. Це здається занадто простим, щоб його переглянули.
примо

1. Мені цікаво: чи вимагало будь-яке із тестових значень у вашому аналізі складності обхід першої широти? 2. Стаття у Вікіпедії, з якою ви пов’язані, є дещо заплутаною. Він згадує алгоритм Рабіна-Шалліта, але є прикладом для зовсім іншого. 3. Було б цікаво подивитися, коли саме алгоритм Рабіна-Шалліта перевершить ваш, я вважаю, що тести на первинність на практиці досить дорогі.
Денніс

1. Не один. 2. Тут я отримав свою інформацію (тобто, що цей алгоритм існує); Я не бачив аналізу і навіть не читав статті. 3. Крива стає настільки крутою в районі 160, що насправді не має значення, наскільки «повільним» є O (log²n) , він все ще перетинатиметься приблизно в цій точці.
примо

1
Друга посилання у питанні пояснює, як реалізувати Рабіна-Шалліта, але це не говорить про складність. Ця відповідь на MathOverflow дає хороший підсумок статті. До речі, ви знову відкрили алгоритм, який використовував Готфрід Рукл в 1911 році ( посилання ).
Денніс

6

Пітон 3 (177)

N=int(input())
k=1
while N%4<1:N//=4;k*=2
n=int(N**.5)
R=range(int(2*n**.5)+1)
print([(a*k,b*k,c*k,d*k)for d in R for c in R for b in R for a in[n,n-1]if a*a+b*b+c*c+d*d==N][0])

Після того як ми зменшимо вхідне Nзначення, яке не ділиться на 4, воно повинно бути вираженим як сума чотирьох квадратів, де один з них є або найбільшим можливим значенням, a=int(N**0.5)або одним меншим за це, залишаючи лише невелику залишок для суми трьох інших квадратів. піклуватися про. Це значно скорочує простір пошуку.

Ось підтвердження, що пізніше цей код завжди знаходить рішення. Ми хочемо знайти aтак, що n-a^2є сумою трьох квадратів. З теореми трьох квадратів Легенди число є сумою трьох квадратів, якщо це не форма 4^j(8*k+7). Зокрема, такі числа є або 0, або 3 (модуль 4).

Ми покажемо , що не два поспіль aможе зробити кількість залишилися N-a^2не мають таку форму , як для послідовних значень .. Ми можемо зробити це, просто зробити таблицю aі по Nмодулю 4, відзначивши , що , N%4!=0так як ми всі витягнутий повноваження 4 з N.

  a%4= 0123
      +----
     1|1010
N%4= 2|2121  <- (N-a*a)%4
     3|3232

Оскільки немає двох послідовних aпоступок (N-a*a)%4 in [0,3], один з них є безпечним для використання. Отже, ми з жадібністю використовуємо якнайбільше можливостей за nдопомогою n^2<=Nі n-1. Оскільки N<(n+1)^2решта, N-a^2яку потрібно подати у вигляді суми трьох квадратів, становить щонайбільше (n+1)^2 -(n-1)^2, що дорівнює 4*n. Отже, досить перевірити лише значення до 2*sqrt(n), а саме такий діапазон R.

Можна додатково оптимізувати час роботи, зупиняючись після єдиного рішення, обчислюючи, а не повторюючи останнє значення dта шукаючи лише серед значень b<=c<=d. Але навіть без цих оптимізацій найгірший екземпляр, який я міг знайти закінченим за 45 секунд на своїй машині.

Ланцюг "для х в R" невдалий. Можливо, це може бути скорочено підстановкою рядків або заміною ітерацією над одним індексом, який кодує (a, b, c, d). Імпортувати itertools виявилося не варто.

Змінити Останні зміни внесено int(2*n**.5)+1з 2*int(n**.5)+2зробити аргумент чистіше, то ж кількість символів.


Це не працює для мене ...5 => (2, 1, 0, 0)
Гаррі Бісл

Як не дивно, це працює для мене: я 5 => (2, 1, 0, 0)біжу на Ideone 3.2.3 або в режимі очікування 3.2.2. Що ви отримуєте?
xnor

1
@xnor BritishColour отримує 5 => (2, 1, 0, 0). Ви навіть читали коментар? (Зараз у нас є 3 коментарі поспіль, які містять фрагмент коду. Чи можемо ми продовжувати рух?)
Джастін

@Quincunx Якщо ми хочемо розшифрувати 5 => (2, 1, 0, 0), це означає 2^2 + 1^2 + 0^2 + 0^2 = 5. Отже, так, ми можемо.
Доктор Ребму

1
Quincunx, я читав коментар @ BritishColour, і наскільки я бачу, 5 => (2, 1, 0, 0)це правильно. Приклади у питанні вважають, що 0 ^ 2 = 0 є дійсним квадратним числом. Тому я інтерпретував (як я вважаю, xnor), що британський Колір отримав щось інше. Британський колір, як ви вже не відповіли знову, чи можна припустити, що ви насправді отримуєте 2,1,0,0?
Рівень Св. Св

5

CJam , 91 90 74 71 байт

q~{W):W;:N4md!}gmqi257:B_**_{;)_[Bmd\Bmd]_N\{_*-}/mq_i@+\1%}g{2W#*}%`\;

Компактний, але повільніше, ніж мій інший підхід.

Спробуйте в Інтернеті! Вставте код , введіть потрібне ціле число в Input і натисніть кнопку « Виконати» .

Фон

Ця публікація розпочалася як відповідь GolfScript 99 байт . Поки ще не було місця для вдосконалення, GolfScript не вистачає вбудованої функції sqrt. Я зберігав версію GolfScript до версії 5 , оскільки вона була дуже схожа на версію CJam.

Однак для оптимізації з моменту версії 6 потрібні оператори, які недоступні в GolfScript, тому замість публікації окремих пояснень для обох мов я вирішив відмовитись від менш конкурентоспроможної (і значно повільнішої) версії.

Реалізований алгоритм обчислює числа по грубій силі:

  1. Для введення mзнайдіть Nі Wтаке, щоm = 4**W * N .

  2. Встановити i = 257**2 * floor(sqrt(N/4)).

  3. Встановити i = i + 1.

  4. Знайдіть цілі числа j, k, lтакі, що i = 257**2 * j + 257 * k + l, деk, l < 257 .

  5. Перевірте, чи d = N - j**2 - k**2 - l**2 ідеальний квадрат.

  6. Якщо це не так, поверніться до кроку 3.

  7. Друк 2**W * j, 2**W * k, 2**W * l, 2**W * sqrt(m) .

Приклади

$ TIME='\n%e s' time cjam lagrange.cjam <<< 999999999
[27385 103 15813 14]
0.46 s
$ TIME='\n%e s' time cjam lagrange.cjam <<< 805306368
[16384 16384 0 16384]
0.23 s

Часи синхронізації відповідають Intel Core i7-4700MQ.

Як це працює

q~              " Read and interpret the input. ";
{
  W):W;         " Increment “W” (initially -1). ";
  :N            " Save the integer on the stack in “N”. ';
  4md!          " Push N / 4 and !(N % 4). ";
}g              " If N / 4 is an integer, repeat the loop.
mqi             " Compute floor(sqrt(N/4)). ";
257:B_**        " Compute i = floor(sqrt(N)) * 257**2. ";
_               " Duplicate “i” (dummy value). ";
{               " ";
  ;)_           " Pop and discard. Increment “i”. ";
  [Bmd\Bmd]     " Push [ j k l ], where i = 257**2 * j + 257 * k + l and k, l < 257. ";
  _N\           " Push “N” and swap it with a copy of [ j k l ]. ";
  {_*-}/        " Compute m = N - j**2 - k**2 - l**2. ";
  mq            " Compute sqrt(m). ";
  _i            " Duplicate sqrt(m) and compute floor(sqrt(m)). ";
  @+\           " Form [ j k l floor(sqrt(m)) ] and swap it with sqrt(m). ";
  1%            " Check if sqrt(m) is an integer. ";
}g              " If it is, we have a solution; break the loop. ";
{2W#*}%         " Push 2**W * [ j k l sqrt(m) ]. ";
`\;             " Convert the array into a string and discard “i”. ";

2

С, 228

Це засновано на алгоритмі на сторінці Вікіпедії, який є O (n) грубою силою.

n,*l,x,y,i;main(){scanf("%d",&n);l=calloc(n+1,8);
for(x=0;2*x*x<=n;x++)for(y=x;(i=x*x+y*y)<=n;y++)l[2*i]=x,l[2*i+1]=y;
for(x=0,y=n;;x++,y--)if(!x|l[2*x+1]&&l[2*y+1]){
printf("%d %d %d %d\n",l[2*x],l[2*x+1],l[2*y],l[2*y+1]);break;}}

2

GolfScript, 133 130 129 байт

~{.[4*{4/..4%1$!|!}do])\}:r~,(2\?:f;{{..*}:^~4-1??n*,}:v~)..
{;;(.^3$\-r;)8%!}do-1...{;;;)..252/@252%^@^@+4$\-v^@-}do 5$]{f*}%-4>`

Швидкий, але тривалий. Новий рядок можна видалити.

Спробуйте в Інтернеті. Зауважте, що онлайн-перекладач має обмеження в 5 секунд, тому він може працювати не для всіх номерів.

Фон

Алгоритм користується перевагою теореми про триквадрати Легенда , яка говорить про те, що кожне натуральне число n, яке не має виду

                                                                   n = 4 ** a * (8b + 7)

можна виразити сумою трьох квадратів.

Алгоритм виконує наступні дії:

  1. Виразіть число як 4**i * j.

  2. Знайти найбільше ціле число , kтакі , що k**2 <= jі j - k**2задовольняє умові три квадратних теореми Лежандра.

  3. Встановити i = 0.

  4. Перевірте, чиj - k**2 - (i / 252)**2 - (i % 252)**2 ідеальний квадрат.

  5. Якщо це не так, збільшуйте iта поверніться до кроку 4.

Приклади

$ TIME='%e s' time golfscript legendre.gs <<< 0
[0 0 0 0]
0.02 s
$ TIME='%e s' time golfscript legendre.gs <<< 123456789
[32 0 38 11111]
0.02 s
$ TIME='%e s' time golfscript legendre.gs <<< 999999999
[45 1 217 31622]
0.03 s
$ TIME='%e s' time golfscript legendre.gs <<< 805306368
[16384 0 16384 16384]
0.02 s

Часи синхронізації відповідають Intel Core i7-4700MQ.

Як це працює

~              # Interpret the input string. Result: “n”
{              #
  .            # Duplicate the topmost stack item.
  [            #
    4*         # Multiply it by four.
    {          #
      4/       # Divide by four.
      ..       # Duplicate twice.
      4%1$     # Compute the modulus and duplicate the number.
      !|!      # Push 1 if both are truthy.
    }do        # Repeat if the number is divisible by four and non-zero.
  ]            # Collect the pushed values (one per iteration) into an array.
  )\           # Pop the last element from the array and swap it with the array.
}:r~           # Save this code block as “r” and execute it.
,(2\?          # Get the length of the array, decrement it and exponentiate.
:f;            # Save the result in “f”.
               # The topmost item on the stack is now “j”, which is not divisible by 
               # four and satisfies that n = f**2 * j.
{              #
  {..*}:^~     # Save a code block to square a number in “^” and execute it.
  4-1??        # Raise the previous number to the power of 1/4.
               # The two previous lines compute (x**2)**(1/4), which is sqrt(abs(x)).
  n*,          # Repeat the string "\n" that many times and compute its length.
               # This casts to integer. (GolfScript doesn't officially support Rationals.)
}:v~           # Save the above code block in “v” and execute it.
)..            # Undo the first three instructions of the loop.
{              #
   ;;(         # Discard two items from the stack and decrement.
   .^3$\-      # Square and subtract from “n”.
   r;)8%!      # Check if the result satisfies the hypothesis of the three-square theorem.
}do            # If it doesn't, repeat the loop.
-1...          # Push 0 (“i”) and undo the first four instructions of the loop.
{              #
  ;;;)         # Discard two items from the stack and increment “i”.
  ..252/@252%  # Push the digits of “i” in base 252.
  ^@^@+4$\-    # Square both, add and subtract the result 
  v^@-         # Take square root, square and compare.
}do            # If the difference is a perfect square, break the loop.
5$]            # Duplicate the difference an collect the entire stack into an array.
{f*}%          # Multiply very element of the array by “f”.
-4>            # Reduce the array to its four last elements (the four numbers).
`              # Convert the result into a string.

1
Я не розумів j-k-(i/252)-(i%252). З ваших коментарів (я насправді не можу прочитати код), схоже, ви маєте на увазі j-k-(i/252)^2-(i%252)^2. BTW, еквівалент j-k-(i/r)^2-(i%r)^2де r = sqrt (k) може зберегти кілька символів (і, здається, працює без проблем навіть для k = 0 в моїй програмі C)
Level River St

@steveverrill: Так, я помилився. Дякую, що помітили. Це повинно бути j-k^2-(i/252)^2-(i%252)^2. Я все ще чекаю, коли ОП уточнить, чи 0 є дійсним введенням чи ні. Ваша програма дає 1414 -nan 6 4.000000вклад 0.
Денніс

Я говорю про свою нову програму, використовуючи теорему Легенда, яку я ще не опублікував. Схоже, він ніколи не викликає код із% або /, коли у мене еквівалент k = 0, тому це не викликає проблем. Ви побачите, коли я опублікую його. Рада, що ви запустили мою стару програму. У вас була пам'ять, щоб скласти повну таблицю 2 Гб в оборотах 1, і скільки часу це зайняло?
Рівень р. Св

Так, компілятор C може вести себе досить несподівано при оптимізації. У GolfScript 0/=> аварія! : P Я запустив ваш rev 1 на своєму ноутбуці (i7-4700MQ, 8 Гб оперативної пам’яті). В середньому час виконання - 18,5 секунди.
Денніс

Вау, це 18,5 секунд, включаючи побудову столу? На моїй машині це займає понад 2 хвилини. Я бачу, проблема полягає в управлінні пам'яттю Windows. Замість того, щоб давати програмі 2 Гб, які їй потрібні відразу, вони дають її невеликими шматками, тому вона повинна робити багато зайвих обмінів, поки не буде виділено повних 2 ГБ. Насправді пошук відповіді на введення користувача відбувається набагато швидше, оскільки тоді програма не повинна просити пам'яті.
Рівень р. Св

1

Вип. 1: С, 190

a,z,m;short s[15<<26];p(){m=s[a=z-a];printf("%d %f ",m,sqrt(a-m*m));}
main(){m=31727;for(a=m*m;--a;s[z<m*m?z:m*m]=a%m)z=a/m*(a/m)+a%m*(a%m);scanf("%d",&z);for(;a*!s[a]||!s[z-a];a++);p();p();}

Це ще більше голодної пам'яті, ніж оберт 0. Такий же принцип: побудуйте таблицю з позитивним значенням для всіх можливих сум 2 квадратів (і нулем для тих чисел, які не є сумами двох квадратів), а потім шукайте її.

У цій версії використовуйте масив shortзамість того, charщоб зберігати звернення, тож я можу зберігати корінь однієї з пар квадратів у таблиці замість просто прапора. Це pзначно спрощує функцію (для декодування суми в 2 квадрати), оскільки немає необхідності в циклі.

Windows має обмеження на 2 Гб для масивів. Я можу обійти те, з short s[15<<26]яким є масив з 1006632960 елементів, достатньо, щоб відповідати специфікації. На жаль, загальний розмір виконання програми все ще перевищує 2 Гб, і (незважаючи на налаштування параметрів ОС), я не зміг перебігти цей розмір (хоча це теоретично можливо.) Найкраще, що я можу зробити, це short s[14<<26](939524096 елементів.)m*m Повинні бути суворо менше, ніж це (30651 ^ 2 = 939483801.) Тим не менш, програма працює ідеально і повинна працювати на будь-якій ОС, яка не має цього обмеження.

Невикористаний код

a,z,m;
short s[15<<26];     
p(){m=s[a=z-a];printf("%d %f ",m,sqrt(a-m*m));}      
main(){       
 m=31727;             
 for(a=m*m;--a;s[z<m*m?z:m*m]=a%m)   //assignment to s[] moved inside for() is executed after the following statement. In this rev excessively large values are thrown away to s[m*m].
   z=a/m*(a/m)+a%m*(a%m);            //split a into high and low half, calculate h^2+l^2.                                  
 scanf("%d",&z); 
 for(;a*!s[a]||!s[z-a];a++);         //loop until s[a] and s[z-a] both contain an entry. s[0] requires special handling as s[0]==0, therefore a* is included to break out of the loop when a=0 and s[z] contains the sum of 2 squares.
 p();                                //print the squares for the first sum of 2 squares 
 p();}                               //print the squares for the 2nd sum of 2 squares (every time p() is called it does a=z-a so the two sums are exchanged.) 

Вир 0 С, 219

a,z,i,m;double t;char s[1<<30];p(){for(i=t=.1;(m=t)-t;i++)t=sqrt(a-i*i);printf("%d %f ",i-1,t);}
main(){m=1<<15;for(a=m*m;--a;){z=a/m*(a/m)+a%m*(a%m);s[z<m*m?z:0]=1;}scanf("%d",&z);for(;1-s[a]*s[z-a];a++);p();a=z-a;p();}

Це звірячий голодний звір. Він займає масив 1 ГБ, обчислює всі можливі суми по 2 квадрата і зберігає прапор для кожного в масиві. Потім для введення користувача z він шукає масив за двома сумами з 2 квадратів a і za.

функція pпотім reconsitutes початкові квадрати , які були використані , щоб зробити суми 2 квадратів aі z-aі виводить їх, першу з кожної пари в вигляді цілого числа, другий , як подвійні (якщо вона є , щоб бути все цілими числами , які необхідні ще два символи, t> m=t.)

Програма займає кілька хвилин, щоб скласти таблицю сум квадратів (я думаю, це пов’язано з проблемами управління пам'яттю. Я бачу, що розподіл пам'яті відбувається повільно, а не підстрибує, як можна було очікувати.) Однак, як тільки це буде зроблено дає відповіді дуже швидко (якщо потрібно обчислити кілька чисел, програму scanfдалі можна поставити в цикл.

неліфований код

a,z,i,m;
double t;
char s[1<<30];                              //handle numbers 0 up to 1073741823
p(){
 for(i=t=.1;(m=t)-t;i++)t=sqrt(a-i*i);      //where a contains the sum of 2 squares, search until the roots are found
 printf("%d %f ",i-1,t);}                   //and print them. m=t is used to evaluate the integer part of t. 

main(){       
 m=1<<15;                                   //max root we need is sqrt(1<<30);
 for(a=m*m;--a;)                            //loop m*m-1 down to 1, leave 0 in a
   {z=a/m*(a/m)+a%m*(a%m);s[z<m*m?z:0]=1;}  //split a into high and low half, calculate h^2+l^2. If under m*m, store flag, otherwise throw away flag to s[0]
 scanf("%d",&z);
 for(;1-s[a]*s[z-a];a++);                   //starting at a=0 (see above) loop until flags are found for sum of 2 squares of both (a) and (z-a)
 p();                                       //reconsitute and print the squares composing (a)
 a=z-a;                                     //assign (z-a) to a in order to...
 p();}                                      //reconsitute and print the squares composing (z-a)  

Приклад виведення

Перший - це питання. Другий був обраний як важкий для пошуку. У цьому випадку програма повинна шукати аж 8192 ^ 2 + 8192 ^ 2 = 134217728, але потрібно лише кілька секунд після побудови таблиці.

123456789
0 2.000000 3328 10601.000000

805306368
8192 8192.000000 8192 24576.000000

Чи не слід додати прототип для sqrt?
edc65

@ edc65 Я використовую компілятор GCC (який призначений для Linux, але у мене на комп'ютері Windows встановлено середовище Cygwin Linux). Це означає, що мені не потрібно ставити #include <stdio.h>(для scanf / printf) або #include <math.h>(для sqrt.) компілятор автоматично зв'язує необхідні бібліотеки. Я повинен подякувати Деннісу за це (він сказав мені з цього питання codegolf.stackexchange.com/a/26330/15599 ) Найкраща порада з гольфу, яку я коли-небудь мав.
Рівень річки Св

Мені вже було цікаво, чому в пов'язаних питаннях з'явився Хант Вемпус. :) До речі, я не знаю, що GCC використовує у Windows, але лінкер GNU не пов'язує математичну бібліотеку автоматично, з або без include. Щоб скласти на Linux, вам потрібен прапор-lm
Денніс

@Dennis , що цікаво, вона включає stdioі кілька інших бібліотек, але не mathнавіть зinclude ? З якого я розумію, якщо ви поставите прапор компілятора, вам це includeвсе одно не потрібно ? Ну, це працює для мене, тому я не скаржусь, ще раз дякую за пораду. До речі, я сподіваюся опублікувати зовсім іншу відповідь, скориставшись теоремою Легенда (але вона все одно використовуватиме a sqrt.)
рівень річки Св

-lmвпливає на лінкер, а не на компілятор. gccвирішує не вимагати прототипів для функцій, які він "знає", тому він працює з включеними або без них. Однак файли заголовків забезпечують лише прототипи функцій, а не самі функції. У Linux (але не в Windows), libm математичної бібліотеки не є частиною стандартних бібліотек, тому вам доведеться вказуватиld посилання на неї.
Денніс

1

Математика, 138 символів

Отже, виходить, що це дає негативні та уявні результати для певних входів, як вказувало edc65 (наприклад, 805306368), тому це не є правильним рішенням. Я покину це, і, можливо, якщо я справді ненавиджу свій час, я повернусь назад і спробую виправити це.

S[n_]:=Module[{a,b,c,d},G=Floor@Sqrt@#&;a=G@n;b:=G[n-a^2];c:=G[n-a^2-b^2];d:=G[n-a^2-b^2-c^2];While[Total[{a,b,c,d}^2]!=n,a-=1];{a,b,c,d}]

Або, незайняті:

S[n_] := Module[{a, b, c, d}, G = Floor@Sqrt@# &;
 a = G@n;
 b := G[n - a^2];
 c := G[n - a^2 - b^2];
 d := G[n - a^2 - b^2 - c^2];
 While[Total[{a, b, c, d}^2] != n, a -= 1];
 {a, b, c, d}
]

Я не надто жорстко дивився на алгоритми, але я думаю, що це та сама ідея. Я просто придумав очевидне рішення і налаштував його, поки воно не спрацювало. Я перевірив його на всі числа від 1 до одного мільярда і ... він працює. Тест займає лише близько 100 секунд на моїй машині.

Приємним є те, що оскільки b, c і d визначаються із запізненням на призначення :=, їх не потрібно переосмислювати при зменшенні. Це врятувало кілька зайвих рядків, які я мав раніше. Я можу подальший гольф і вкладати зайві частини, але ось перший проект.

О, і ви запускаєте його як S@123456789і ви можете протестувати за допомогою {S@#, Total[(S@#)^2]} & @ 123456789або # == Total[(S@#)^2]&[123456789]. Вичерпний тест є

n=0;
AbsoluteTiming@ParallelDo[If[e != Total[(S@e)^2], n=e; Abort[]] &, {e, 1, 1000000000}]
n

Раніше я використовував заяву Print [], але це сильно уповільнило її, хоча він ніколи не викликає. Піди розберися.


Це дійсно чисто! Я здивований, що досить просто взяти кожну цінність, але першу якомога більше. Для гольфу, ймовірно, коротше зберегти n - a^2 - b^2 - c^2як змінну і перевірити, що d^2вона дорівнює.
xnor

2
Це справді працює? Яке рішення знаходить для введення 805306368?
edc65

S [805306368] = {- 28383, 536 I, 32 I, I}. Ага. Це робить 805306368, коли підсумувати його, але очевидно, що в цьому алгоритмі є проблема. Напевно, мені доведеться зараз це відкликати; дякую, що
вказали

2
Цифри, які виходять з ладу, начебто поділяються великими потужностями 2. Зокрема, вони здаються формою a * 4^(2^k)для k>=2, витягнувши всі потужності 4, так що aце не кратне 4 (але може бути парним). Більше того, кожен aабо 3 мод 4, або вдвічі більше такого числа. Найменший - 192.
xnor

1

Haskell 123 + 3 = 126

main=getLine>>=print.f.read
f n=head[map(floor.sqrt)[a,b,c,d]|a<-r,b<-r,c<-r,d<-r,a+b+c+d==n]where r=[x^2|x<-[0..n],n>=x^2]

Проста груба сила над попередньо розрахованими квадратами.

Для цього потрібен -Oваріант компіляції (я додав для цього 3 символи). На найгірший випадок 999950883 потрібно менше 1 хвилини.

Тестується лише на GHC.


1

C: 198 символів

Я, мабуть, можу стиснути його до трохи більше 100 символів. Що мені подобається в цьому рішенні - це мінімальна кількість мотлоху, просто звичайна петля, яка робить те, що має робити цикл for-loop (що має бути божевільним).

i,a,b,c,d;main(n){for(scanf("%d",&n);a*a+b*b-n?a|!b?a*a>n|a<b?(--a,b=1):b?++b:++a:(a=b=0,--n,++i):c*c+d*d-i?c|!d?c*c>i|c<d?(--c,d=1):d?++d:++c:(a=b=c=d=0,--n,++i):0;);printf("%d %d %d %d",a,b,c,d);}

І сильно задекорований:

#include <stdio.h>

int n, i, a, b, c, d;

int main() {
    for (
        scanf("%d", &n);
        a*a + b*b - n
            ? a | !b
                ? a*a > n | a < b
                    ? (--a, b = 1)
                    : b
                        ? ++b
                        : ++a
                : (a = b = 0, --n, ++i)
            : c*c + d*d - i
                ? c | !d
                    ? c*c > i | c < d
                        ? (--c, d = 1)
                        : d
                            ? ++d
                            : ++c
                    : (a = b = c = d = 0, --n, ++i)
                : 0;
    );
    printf("%d %d %d %d\n", a, b, c, d);
    return 0;
}

Редагувати: Це недостатньо швидко для всіх даних, але я повернусь до іншого рішення. Я дозволю, що цей порядок тризначної операції залишиться зараз.


1

Ред Б: С, 179

a,b,c,d,m=1,n,q,r;main(){for(scanf("%d",&n);n%4<1;n/=4)m*=2;
for(a=sqrt(n),a-=(3+n-a*a)%4/2;r=n-a*a-b*b-c*c,d=sqrt(r),d*d-r;c=q%256)b=++q>>8;
printf("%d %d %d %d",a*m,b*m,c*m,d*m);}

Дякуємо @Dennis за вдосконалення. Решта відповіді нижче не оновлюється у версії А.

Випуск А: С, 195

a,b,c,d,n,m,q;double r=.1;main(){scanf("%d",&n);for(m=1;!(n%4);n/=4)m*=2;a=sqrt(n);a-=(3+n-a*a)%4/2;
for(;(d=r)-r;q++){b=q>>8;c=q%256;r=sqrt(n-a*a-b*b-c*c);}printf("%d %d %d %d ",a*m,b*m,c*m,d*m);}

Набагато швидше, ніж моя інша відповідь і з набагато меншою пам’яттю!

Для цього використовується http://en.wikipedia.org/wiki/Legendre%27s_three-square_theorem . Будь-яке число, яке не має такої форми, може бути виражене сумою 3 квадратів (я називаю це забороненою формою):

4^a*(8b+7), or equivalently 4^a*(8b-1)

Зауважте, що всі непарні квадратні числа мають форму (8b+1) а всі парні числа - поверхнево 4b. Однак це приховує той факт, що всі парні числа мають форму 4^a*(odd square)==4^a*(8b+1). Як результат, 2^x-(any square number < 2^(x-1))непарні xзавжди будуть мати заборонену форму. Отже, ці цифри та їх кратність є складними випадками, і тому так багато програм тут розділяють повноваження 4, як перший крок.

Як зазначено у відповіді @ xnor, N-a*aне може бути забороненої форми для двох послідовних значеньa . Нижче я представляю спрощену форму його таблиці. Крім того, що після поділу на 4N%4 не можна дорівнювати 0, зауважте, що існує лише 2 можливих значення для (a*a)%4.

(a*a)%4= 01
        +--
       1|10
  N%4= 2|21  <- (N-a*a)%4
       3|32

Отже, ми хочемо уникати значень (N-a*a) які можуть бути забороненою формою, а саме тих, де (N-a*a)%4дорівнює 3 або 0. Як видно, це не може відбуватися для одного і Nз непарними, і з парними (a*a).

Отже, мій алгоритм працює так:

1. Divide out powers of 4
2. Set a=int(sqrt(N)), the largest possible square
3. If (N-a*a)%4= 0 or 3, decrement a (only once)
4. Search for b and c such that N-a*a-b*b-c*c is a perfect square

Мені особливо подобається те, як я роблю крок 3. Я додаю 3 N , так що декремент потрібно, якщо (3+N-a*a)%4 =3 або 2. (але не 1 або 0.) Розділіть це на 2, і всю роботу можна виконати досить простим виразом .

Невикористаний код

Зверніть увагу на один forцикл qі використовуйте поділ / модуль для отримання значень bта cз нього. Я намагався використовувати aяк дільник замість 256 для збереження байтів, але іноді значення aне було правильним і програма висіла, можливо, на невизначений термін. 256 був найкращим компромісом, який я можу використовувати >>8замість /256поділу.

a,b,c,d,n,m,q;double r=.1;
main(){
  scanf("%d",&n);
  for(m=1;!(n%4);n/=4)m*=2;
  a=sqrt(n);
  a-=(3+n-a*a)%4/2;
  for(;(d=r)-r;q++){b=q>>8;c=q%256;r=sqrt(n-a*a-b*b-c*c);}
  printf("%d %d %d %d ",a*m,b*m,c*m,d*m);}

Вихідні дані

Цікава примха полягає в тому, що якщо ви введете квадратне число, N-(a*a)= 0. Але програма виявляє, що 0%4= 0, і зменшення на наступний квадрат вниз. В результаті введення квадратних чисел завжди розкладаються на групу менших квадратів, якщо вони не мають форми 4^x.

999999999
31621 1 161 294

805306368
16384 0 16384 16384

999950883
31621 1 120 221

1
0 0 0 1

2
1 0 0 1

5
2 0 0 1

9
2 0 1 2

25
4 0 0 3

36
4 0 2 4

49
6 0 2 3

81
8 0 1 4

121
10 1 2 4

Дивовижний! 0,003 с на кожен вхід! Ви можете отримати ці 5 символів назад: 1. Заявіть m=1раніше main. 2. Виконати scanfу forвиписці. 3. Використовуйте floatзамість double. 4. n%4<1коротше, ніж !(n%4). 5. У рядку формату printf є застаріле місце.
Денніс


Дякуємо за поради! n-=a*aне працює, тому aщо після цього можна змінити (він дає неправильні відповіді і зависає в невеликій кількості випадків, наприклад 100 + 7 = 107.) Я включив все інше. Було б непогано щось скоротити, printfале я думаю, що єдиною відповіддю є зміна мови. Ключ до швидкості - швидко визначитися a. Це написано на C і з пошуковим простором менше 256 ^ 2, це, мабуть, найшвидша програма тут.
Рівень річки Св

Правильно, вибачте. Скорочення printfоператора здається важким без використання макросу або масиву, який би додав групу в іншому місці. Зміна мов здається "простим" способом. Ваш підхід би важив 82 байти в CJam.
Денніс

0

JavaScript - 175 191 176 173 символів

Груба сила, але швидка.

Редагувати швидко, але недостатньо для неприємного введення. Мені довелося додати перший крок зменшення на множення 4.

Редагуйте 2 Позбудьтесь функції, виведіть всередину циклу, а потім примусьте вийти з положення

Редагувати 3 0 Неправильний ввід

v=(p=prompt)();for(m=1;!(v%4);m+=m)v/=4;for(a=-~(q=Math.sqrt)(v);a--;)for(w=v-a*a,b=-~q(w);b--;)for(x=w-b*b,c=-~q(x);c--;)(d=q(x-c*c))==~~d&&p([m*a, m*b, m*c, m*d],a=b=c='')

Безголівки:

v = prompt();

for (m = 1; ! (v % 4); m += m) 
{
  v /= 4;
}
for (a = - ~Math.sqrt(v); a--;) /* ~ force to negative integer, changing sign lead to original value + 1 */
{
  for ( w = v - a*a, b = - ~Math.sqrt(w); b--;)
  {
    for ( x = w - b*b, c = - ~Math.sqrt(x); c--;)
    {
      (d = Math.sqrt(x-c*c)) == ~~d && prompt([m*a, m*b, m*c, m*d], a=b=c='') /* 0s a,b,c to exit loop */
    }
  }
}

Приклад виведення

123456789
11111,48,10,8

805306368
16384,16384,16384,0
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.