Quattro quadrati insieme


19

Il teorema di quattro quadrati di Lagrange ci dice che qualsiasi numero naturale può essere rappresentato come la somma di quattro numeri quadrati. Il tuo compito è scrivere un programma che faccia questo.

Input: un numero naturale (inferiore a 1 miliardo)

Output: quattro numeri i cui quadrati si sommano a quel numero (l'ordine non ha importanza)

Nota: non è necessario eseguire una ricerca di forza bruta! Dettagli qui e qui . Se esiste una funzione che banalizza questo problema (lo determinerò), non è permesso. Sono consentite funzioni prime automatizzate e radice quadrata. Se esiste più di una rappresentazione, ognuna va bene. Se hai scelto di fare forza bruta, deve funzionare entro un tempo ragionevole (3 minuti)

input di esempio

123456789

output del campione (o va bene)

10601 3328 2 0
10601 3328 2

Potrei fare la forza bruta anche se questo accorcia il mio codice?
Martin Ender,

@ m.buettner Sì, ma dovrebbe gestire grandi numeri
qwr

@ m.buettner Leggi il post, qualsiasi numero naturale inferiore a 1 miliardo
qwr

Ah scusa se l'è trascurato.
Martin Ender,

2
I numeri di @Dennis Natural in questo caso non includono 0.
qwr

Risposte:


1

CJam, 50 byte

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

La mia terza (e ultima, promessa) risposta. Questo approccio si basa fortemente sulla risposta di primo .

Provalo online nell'interprete CJam .

uso

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

sfondo

  1. Dopo aver visto l'algoritmo aggiornato di primo, ho dovuto vedere come avrebbe ottenuto il punteggio di un'implementazione di CJam:

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

    Solo 58 byte! Questo algoritmo viene eseguito in un tempo quasi costante e non presenta molte variazioni per valori diversi di N. Cambiamo quello ...

  2. Invece di iniziare floor(sqrt(N))e diminuire, possiamo iniziare 1e incrementare. Ciò consente di risparmiare 4 byte.

    li{W):W;:N4md!}g;0_{;)__*N\-[{_mqi__*@\-}3*])}g+2W#f*p
    
  3. Invece di esprimere Ncome 4**a * b, possiamo esprimerlo come p**(2a) * b- dove pè il più piccolo fattore primo di N- per salvare 1 byte in più.

    li_mF0=~2/#:J_*/:N!_{;)__*N\-[{_mqi__*@\-}3*])}g+Jf*p
    
  4. Precedente modifica ci permette di cambiare leggermente la realizzazione (senza toccare l'algoritmo stesso): Invece di dividere Nda p**(2a)e moltiplicare la soluzione p**a, si può limitare direttamente le possibili soluzioni ai multipli di p**a. Ciò consente di risparmiare altri 2 byte.

    li:NmF0=~2/#:J!_{;J+__*N\-[{_mqJ/iJ*__*@\-}3*])}g+`
    
  5. Non limitare il primo numero intero ai multipli di p**asalva un byte aggiuntivo.

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

Algoritmo finale

  1. Trova ae btale che N = p**(2a) * b, dove bnon è un multiplo di p**2ed pè il più piccolo fattore primo di N.

  2. Set j = p**a.

  3. Set k = floor(sqrt(N - j**2) / A) * A.

  4. Set l = floor(sqrt(N - j**2 - k**2) / A) * A.

  5. Set m = floor(sqrt(N - j**2 - k**2 - l**2) / A) * A.

  6. Se N - j**2 - k**2 - l**2 - m**2 > 0, imposta j = j + 1e torna al passaggio 3.

Questo può essere implementato come segue:

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.           ";

Punti di riferimenti

Ho eseguito tutte e 5 le versioni su tutti i numeri interi positivi fino a 999.999.999 sul mio Intel Core i7-3770, ho misurato i tempi di esecuzione e ho contato le iterazioni richieste per trovare una soluzione.

La tabella seguente mostra il numero medio di iterazioni e tempi di esecuzione per un singolo numero intero:

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. Con solo 4 iterazioni e 6,6 micro secondi per intero, l'algoritmo di primo è incredibilmente veloce.

  2. Iniziare da floor(sqrt(N))ha più senso, dal momento che questo ci lascia con valori più piccoli per la somma dei restanti tre quadrati. Come previsto, a partire da 1 è molto più lento.

  3. Questo è un classico esempio di buona idea mal implementata. Per ridurre effettivamente la dimensione del codice, ci affidiamo a mF, che fattorizza l'intero N. Sebbene la versione 3 richieda meno iterazioni rispetto alla versione 2, in pratica è molto più lenta.

  4. Sebbene l'algoritmo non cambi, la versione 4 è molto più lenta. Questo perché esegue un'ulteriore divisione in virgola mobile e una moltiplicazione intera in ogni iterazione.

  5. Per l'input N = p**(2a) ** b, l'algoritmo 5 richiederà (k - 1) * p**a + 1iterazioni, dove kè il numero di iterazioni che l'algoritmo 4 richiede. Se k = 1o a = 0, questo non fa differenza.

    Tuttavia, qualsiasi input del modulo 4**a * (4**c * (8 * d + 7) + 1)può avere prestazioni piuttosto scarse. Per il valore di partenza j = p**a, N - 4**a = 4**(a + c) * (8 * d + 7)in modo che non può essere espresso come somma di tre quadrati. Pertanto, sono necessarie k > 1almeno p**aiterazioni.

    Per fortuna, l'algoritmo originale di primo è incredibilmente veloce e N < 1,000,000,000. Il caso peggiore che ho trovato a mano è 265,289,728 = 4**10 * (4**1 * (7 * 8 + 7) + 1), che richiede 6.145 iterazioni. Il tempo di esecuzione è inferiore a 300 ms sulla mia macchina. In media, questa versione è 13,5 volte più lenta dell'implementazione dell'algoritmo di primo.


"Invece di esprimere Ncome 4**a * b, possiamo esprimerlo come p**(2a) * b." Questo è in realtà un miglioramento . Mi sarebbe piaciuto includerlo, ma è stato molto più lungo (l'ideale è trovare il più grande fattore quadrato perfetto). "Iniziando con 1 e incrementando si salvano 4 byte." Questo è decisamente più lento. Il tempo di esecuzione per un determinato intervallo è 4-5 volte più lungo. "Tutti i numeri interi positivi fino a 999.999.999 sono durati 24,67 ore, dando un tempo medio di esecuzione di 0,0888 millisecondi per intero." Perl ha impiegato solo 2,5 ore per sgretolare l'intera gamma e la traduzione di Python è 10 volte più veloce;)
primo

@primo: Sì, hai ragione. Dividere per p**aè un miglioramento, ma è piccolo. La divisione per il più grande fattore quadrato perfetto fa una grande differenza a partire da 1; è ancora un miglioramento quando si parte dalla parte intera della radice quadrata. L'implementazione costerebbe solo altri due byte. Il tempo di esecuzione abissale sembra essere dovuto ai miei miglioramenti, non a CJam. Eseguirò di nuovo i test per tutti gli algoritmi (incluso quello che hai proposto), contando le iterazioni anziché misurare il tempo della parete. Vediamo quanto tempo che prende ...
Dennis

Trovare il fattore quadrato più grande costa solo 2 byte aggiuntivi ?! Che tipo di stregoneria è questa?
primo

@primo: se l'intero è nello stack, lo 1\scambia con 1 (accumulatore), ne mFspinge la fattorizzazione e {~2/#*}/aumenta ogni fattore primo al suo esponente diviso per due, quindi lo moltiplica con l'accumulatore. Per l'implementazione diretta del tuo algoritmo, ciò aggiunge solo 2 byte. La piccola differenza è principalmente dovuta al modo imbarazzante ho dovuto trovare l'esponente di 4, dal momento che non CJam (sembra) avere un po ' anello ...
Dennis

Ad ogni modo, il benchmark è finito. Il numero totale di iterazioni richieste per fattorizzare tutti i 1.000.000 di numeri interi senza trovare il fattore quadrato più grande è 4.004.829.417, con un tempo di esecuzione di 1,83 ore. La divisione per il fattore quadrato più grande riduce il conteggio delle iterazioni a 3.996.724.799, ma aumenta il tempo a 6,7 ​​ore. Sembra che la fattorizzazione richieda molto più tempo che trovare le piazze ...
Dennis,

7

FRACTRAN: 156 98 frazioni

Dal momento che questo è un classico problema di teoria dei numeri, quale modo migliore per risolverlo che usare i numeri!

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

Accetta input del modulo 2 n × 193 e output 3 a × 5 b × 7 c × 11 d . Potrebbe funzionare in 3 minuti se hai un interprete davvero bravo. Può essere.

... okay, non proprio. Questo sembrava essere un problema divertente da fare in FRACTRAN che ho dovuto provare. Ovviamente, questa non è una soluzione adeguata alla domanda in quanto non richiede i requisiti di tempo (forza bruta) ed è a malapena persino golf, ma ho pensato di pubblicarlo qui perché non tutti i giorni è una domanda di Codegolf può essere fatto in FRACTRAN;)

Suggerimento

Il codice è equivalente al seguente pseudo-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

Mathematica 61 66 51

Sono mostrati tre metodi. Solo il primo approccio soddisfa i requisiti di tempo.


1-FindInstance (51 caratteri)

Ciò restituisce una singola soluzione l'equazione.

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

Esempi e tempistiche

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

Questo funziona anche, ma è troppo lento per soddisfare i requisiti di velocità.

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

Range[0, Floor@Sqrt@n]^2è l'insieme di tutti i quadrati inferiore alla radice quadrata di n(il quadrato più grande possibile nella partizione).

{4}richiede che le partizioni intere siano ncostituite da 4 elementi dell'insieme di quadrati sopra menzionato.

1, all'interno della funzione IntegerPartitionsrestituisce la prima soluzione.

[[1]]rimuove le parentesi graffe esterne; la soluzione è stata restituita come un insieme di un elemento.


f[123456]

{348, 44, 20, 4}


3-PowerRepresentations

PowerRepresentations restituisce tutte le soluzioni al problema dei 4 quadrati. Può anche risolvere somme di altri poteri.

PowersRepresentations restituisce, in meno di 5 secondi, i 181 modi per esprimere 123456789 come la somma di 4 quadrati:

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

sols

Tuttavia, è troppo lento per altre somme.


Caspita, Mathematica fa la forza bruta in fretta. IntegerPartitions sta facendo qualcosa di molto più intelligente di provare ogni combinazione, come la convoluzione DFT sui set? Le specifiche chiedono i numeri, tra l'altro, non i loro quadrati.
xnor

Penso che Mathematica usi la forza bruta, ma probabilmente ha ottimizzato IntegerPartitions. Come puoi vedere dalle tempistiche, la velocità varia notevolmente a seconda che il primo (più grande) numero sia vicino alla radice quadrata di n. Grazie per aver rilevato la violazione delle specifiche nella versione precedente.
DavidC,

Potresti fare un benchmark f[805306368]? Senza prima dividere per potenze di 4, la mia soluzione richiede 0,05 s per 999999999; Ho interrotto il benchmark per 805306368 dopo 5 minuti ...
Dennis,

f[805306368]ritorna {16384, 16384, 16384}dopo 21 minuti. Ho usato {3} al posto di {4}. Il tentativo di risolverlo con una somma di 4 quadrati diversi da zero non ha avuto successo dopo diverse ore di funzionamento.
DavidC,

Non ho accesso a Mathematica, ma da quello che ho letto nel centro di documentazione, IntegerPartitions[n,4,Range[Floor@Sqrt@n]^2dovrebbe funzionare anche. Tuttavia, non penso che dovresti usare il metodo 1 per il tuo punteggio, poiché non è conforme al limite di tempo specificato nella domanda.
Dennis,

7

Perl - 116 byte 87 byte (vedi aggiornamento di seguito)

#!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"

Contando lo shebang come un byte, sono state aggiunte nuove linee per il buonsenso orizzontale.

Qualcosa di una combinazione di invio di .

La complessità media (peggiore?) Del caso sembra essere O (log n) O (n 0,07 ) . Nulla ho trovato corre più lento di 0.001s, e ho controllato l'intero spettro dal 900000000 - 999999999 . Se trovi qualcosa che richiede molto più tempo di quello, ~ 0.1s o più, per favore fatemelo sapere.


Esempio di utilizzo

$ 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

Gli ultimi due di questi sembrano essere scenari peggiori per altre osservazioni. In entrambi i casi, la soluzione mostrata è letteralmente la prima cosa da verificare. Perché 123456789è il secondo.

Se si desidera testare un intervallo di valori, è possibile utilizzare il seguente script:

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);

Ideale se reindirizzato a un file. L'intervallo 1..1000000richiede circa 14 secondi sul mio computer (71000 valori al secondo) e l'intervallo 999000000..1000000000richiede circa 20 secondi (50000 valori al secondo), coerentemente con la complessità media di O (log n) .


Aggiornare

Modifica : si scopre che questo algoritmo è molto simile a quello che è stato usato dai calcolatori mentali per almeno un secolo .

Sin dalla pubblicazione originale, ho controllato tutti i valori nell'intervallo da 1 a 1000000000 . Il comportamento del "caso peggiore" è stato dimostrato dal valore 699731569 , che ha testato un totale di 190 combinazioni prima di arrivare a una soluzione. Se consideri 190 come una piccola costante - e certamente lo faccio - il comportamento peggiore nell'intervallo richiesto può essere considerato O (1) . Cioè, veloce come cercare la soluzione da un tavolo gigante e, in media, molto probabilmente più veloce.

Un'altra cosa però. Dopo 190 iterazioni, nulla di più grande di 144400 non è nemmeno riuscito a superare il primo passaggio. La logica per la prima traversata non ha valore: non viene nemmeno utilizzata. Il codice sopra può essere abbreviato un po ':

#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"

Che esegue solo il primo passaggio della ricerca. Dobbiamo confermare che non ci sono valori inferiori a 144400 che richiedessero il secondo passaggio, tuttavia:

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");
}

In breve, per l'intervallo 1..1000000000 , esiste una soluzione temporale pressoché costante e la stai osservando.


Aggiornamento aggiornato

@Dennis e io abbiamo apportato numerosi miglioramenti a questo algoritmo. Puoi seguire i progressi nei commenti qui sotto e la discussione successiva, se questo ti interessa. Il numero medio di iterazioni per l'intervallo richiesto è sceso da poco più di 4 fino a 1.229 e il tempo necessario per testare tutti i valori per 1..1000000000 è stato migliorato da 18m 54s a 2m 41s. Il caso peggiore in precedenza richiedeva 190 iterazioni; il caso peggiore ora, 854382778 , richiede solo 21 .

Il codice Python finale è il seguente:

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

Questo utilizza due tabelle di correzione pre-calcolate, una da 10kb e l'altra da 253kb. Il codice sopra include le funzioni del generatore per queste tabelle, sebbene queste dovrebbero probabilmente essere calcolate al momento della compilazione.

Una versione con tabelle di correzione di dimensioni più modeste può essere trovata qui: http://codepad.org/1ebJC2OV Questa versione richiede una media di 1.620 iterazioni al termine, con un caso peggiore di 38 , e l'intera gamma funziona in circa 3m 21s. Si compensa un po 'di tempo, usando bit andper bit b correzione , piuttosto che modulo.


miglioramenti

I valori pari hanno maggiori probabilità di produrre una soluzione rispetto ai valori dispari.
L'articolo di calcolo mentale collegato in precedenza nota che se, dopo aver rimosso tutti i fattori di quattro, il valore da scomporre è pari, questo valore può essere diviso per due e la soluzione ricostruita:

Sebbene ciò possa avere senso per il calcolo mentale (i valori più piccoli tendono ad essere più facili da calcolare), non ha molto senso algoritmicamente. Se si prende 256 casuali 4 -tuples, ed esaminare la somma dei quadrati modulo 8 , si noterà che i valori 1 , 3 , 5 e 7 sono ciascuno raggiunti in media 32 volte. Tuttavia, i valori 2 e 6 vengono raggiunti ciascuno 48 volte. Moltiplicare i valori dispari per 2 troverà una soluzione, in media, in iterazioni del 33% in meno. La ricostruzione è la seguente:

Cura deve essere presa che un e b hanno la stessa parità, nonché c e d , ma se una soluzione è stata trovata affatto, un ordinamento corretto è garantito per esistere.

I percorsi impossibili non devono essere controllati.
Dopo aver selezionato il secondo valore, b , potrebbe già essere impossibile che esista una soluzione, dati i possibili residui quadratici per un dato modulo. Invece di verificare comunque, o passare alla successiva iterazione, il valore di b può essere "corretto" diminuendolo del minimo importo che potrebbe portare a una soluzione. Le due tabelle di correzione memorizzano questi valori, uno per b e l'altro per c . L'uso di un modulo più elevato (più precisamente, l'utilizzo di un modulo con relativamente meno residui quadratici) comporterà un miglioramento migliore. Il valore a non necessita di alcuna correzione; modificando n per essere pari, tutti i valori di a sono validi.


1
Questo è incredibile! L'algoritmo finale è probabilmente la più semplice di tutte le risposte, eppure sono sufficienti 190 iterazioni ...
Dennis

@Dennis Sarei molto sorpreso se non fosse stato menzionato altrove. Sembra troppo semplice per essere stato trascurato.
primo

1. Sono curioso: qualcuno dei valori di test nella tua analisi di complessità ha richiesto l'ampiezza trasversale prima? 2. L'articolo di Wikipedia che hai collegato è un po 'confuso. Menziona l'algoritmo Rabin-Shallit, ma fornisce un esempio per uno completamente diverso. 3. Sarebbe interessante vedere quando esattamente l'algoritmo Rabin-Shallit avrebbe sovraperformato il tuo, immagino che i test di primalità siano piuttosto costosi in pratica.
Dennis,

1. Non uno. 2. È qui che ho ottenuto le mie informazioni (ovvero che esiste questo algoritmo); Non ho visto l'analisi, o nemmeno letto il documento. 3. La curva diventa così ripida intorno a 1e60, che in realtà non avrebbe importanza quanto ' O' (log²n) fosse lenta , si attraverserà comunque a quel punto.
primo

1
Il secondo link nella domanda spiega come implementare Rabin-Shallit, ma non parla della complessità. Questa risposta su MathOverflow fornisce un bel riassunto del documento. A proposito, hai riscoperto un algoritmo utilizzato da Gottfried Ruckle nel 1911 ( link ).
Dennis,

6

Python 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])

Dopo aver ridotto l'input Nper non essere divisibile per 4, deve essere espressibile come una somma di quattro quadrati in cui uno di essi è il valore più grande possibile a=int(N**0.5)o uno in meno, lasciando solo un piccolo resto per la somma degli altri tre quadrati prendersi cura di. Ciò riduce notevolmente lo spazio di ricerca.

Ecco una prova dopo che questo codice trova sempre una soluzione. Vogliamo trovare un acosì che n-a^2sia la somma di tre quadrati. Dal teorema dei tre quadrati di Legendre , un numero è la somma di tre quadrati a meno che non sia la forma 4^j(8*k+7). In particolare, tali numeri sono 0 o 3 (modulo 4).

Mostriamo che nessun due consecutivi apossono fare in modo che la quantità rimanente N-a^2abbia una tale forma per entrambi i valori consecutivi. Possiamo farlo semplicemente creando una tabella di ae Nmodulo 4, osservando che N%4!=0perché abbiamo estratto tutti i poteri di 4 su N.

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

Perché nessun due consecutivi adanno (N-a*a)%4 in [0,3], uno di loro è sicuro da usare. Quindi, utilizziamo avidamente il più grande possibile ncon n^2<=N, e n-1. Da allora N<(n+1)^2, il resto N-a^2da rappresentare come somma di tre quadrati è al massimo (n+1)^2 -(n-1)^2, che è uguale 4*n. Quindi, è sufficiente controllare solo i valori fino a 2*sqrt(n), che è esattamente l'intervallo R.

Si potrebbe ulteriormente ottimizzare il tempo di esecuzione fermandosi dopo una singola soluzione, elaborando anziché iterando l'ultimo valore de cercando solo tra i valori b<=c<=d. Ma, anche senza queste ottimizzazioni, l'istanza peggiore che ho trovato è stata completata in 45 secondi sulla mia macchina.

La catena di "for x in R" è sfortunata. Probabilmente può essere abbreviato mediante sostituzione o sostituzione di stringhe ripetendo iterando un singolo indice che codifica (a, b, c, d). L'importazione di itertools non è valsa la pena.

Modifica: modificato da int(2*n**.5)+1da 2*int(n**.5)+2per rendere l'argomento più pulito, conteggio dello stesso personaggio.


Questo non funziona per me ...5 => (2, 1, 0, 0)
Harry Beadle,

Strano, funziona per me: vado 5 => (2, 1, 0, 0)su Ideone 3.2.3 o in Idle 3.2.2. Che cosa ottieni?
xnor

1
@xnor BritishColour ottiene 5 => (2, 1, 0, 0). Hai persino letto il commento? (Ora abbiamo 3 commenti di seguito che hanno quel frammento di codice. Possiamo continuare la sequenza?)
Justin,

@Quincunx Se vogliamo decifrare 5 => (2, 1, 0, 0), significa 2^2 + 1^2 + 0^2 + 0^2 = 5. Quindi sì, possiamo.
Dr. Rebmu,

1
Quincunx, ho letto il commento di BritishColour e, per quanto posso vedere, 5 => (2, 1, 0, 0)è corretto. Gli esempi nella domanda considerano 0 ^ 2 = 0 come un numero quadrato valido. Pertanto ho interpretato (come penso xnor) che British Color ha ottenuto qualcos'altro. Il colore britannico, dato che non hai risposto di nuovo, possiamo presumere che tu effettivamente ottenga 2,1,0,0?
Level River St,

5

CJam , 91 90 74 71 byte

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

Compatto, ma più lento del mio altro approccio.

Provalo online! Incolla il codice , digita il numero intero desiderato in Input e fai clic su Esegui .

sfondo

Questo post è iniziato come una risposta GolfScript da 99 byte . Mentre c'era ancora spazio per miglioramenti, GolfScript non ha la funzione sqrt integrata. Ho mantenuto la versione GolfScript fino alla revisione 5 , poiché era molto simile alla versione CJam.

Tuttavia, le ottimizzazioni dalla revisione 6 richiedono operatori che non sono disponibili in GolfScript, quindi invece di pubblicare spiegazioni separate per entrambe le lingue, ho deciso di abbandonare la versione meno competitiva (e molto più lenta).

L'algoritmo implementato calcola i numeri con la forza bruta:

  1. Per input m, trova Ne Wtale che m = 4**W * N.

  2. Set i = 257**2 * floor(sqrt(N/4)).

  3. Set i = i + 1.

  4. Trova numeri interi j, k, ltali che i = 257**2 * j + 257 * k + l, dove k, l < 257.

  5. Controlla se d = N - j**2 - k**2 - l**2è un quadrato perfetto.

  6. In caso contrario, torna al passaggio 3.

  7. Stampa 2**W * j, 2**W * k, 2**W * l, 2**W * sqrt(m).

Esempi

$ 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

I tempi corrispondono a un Intel Core i7-4700MQ.

Come funziona

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

C, 228

Questo si basa sull'algoritmo nella pagina di Wikipedia, che è una forza bruta 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 byte

~{.[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>`

Veloce ma lungo. La nuova riga può essere rimossa.

Provalo online. Si noti che l'interprete online ha un limite di tempo di 5 secondi, quindi potrebbe non funzionare per tutti i numeri.

sfondo

L'algoritmo sfrutta il teorema di tre quadrati di Legendre , che afferma che ogni numero naturale n che non è della forma

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

può essere espresso come la somma di tre quadrati.

L'algoritmo procede come segue:

  1. Esprimi il numero come 4**i * j.

  2. Trova il numero intero più grande ktale che k**2 <= je j - k**2soddisfi l'ipotesi del teorema di tre quadrati di Legendre.

  3. Set i = 0.

  4. Controlla se j - k**2 - (i / 252)**2 - (i % 252)**2è un quadrato perfetto.

  5. In caso contrario, incrementare ie tornare al passaggio 4.

Esempi

$ 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

I tempi corrispondono a un Intel Core i7-4700MQ.

Come funziona

~              # 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
Non ho capito j-k-(i/252)-(i%252). Dai tuoi commenti (non riesco davvero a leggere il codice), sembra che tu intenda j-k-(i/252)^2-(i%252)^2. A proposito, l'equivalente dij-k-(i/r)^2-(i%r)^2 dove r = sqrt (k) può salvare alcuni caratteri (e sembra funzionare senza problemi anche per k = 0 nel mio programma C.)
Level River St

@steveverrill: Sì, ho fatto un errore. Grazie per averlo notato. Dovrebbe esserej-k^2-(i/252)^2-(i%252)^2 . Sto ancora aspettando che l'OP chiarisca se 0 è un input valido o meno. Il tuo programma fornisce 1414 -nan 6 4.000000input 0.
Dennis,

Sto parlando del mio nuovo programma usando il teorema di Legendre, che non ho ancora pubblicato. Sembra che non chiami mai il codice con% o / quando ho l'equivalente di k = 0, motivo per cui non causa problemi. Vedrai quando lo pubblicherò. Sono contento che tu abbia avviato il mio vecchio programma. Avevi la memoria per compilare la tabella completa da 2 GB nella rev 1 e quanto tempo ci è voluto?
Level River St,

Sì, il compilatore C può comportarsi in modo imprevisto durante l'ottimizzazione. In GolfScript, 0/=> crash! : P Ho eseguito il tuo giro 1 sul mio laptop (i7-4700MQ, 8 GiB RAM). In media, il tempo di esecuzione è di 18,5 secondi.
Dennis,

Wow, sono 18,5 secondi che includono la costruzione del tavolo? Ci vogliono più di 2 minuti sulla mia macchina. Vedo che il problema è la gestione della memoria di Windows. Invece di assegnare immediatamente al programma i 2 GB di cui ha bisogno, lo dà in piccoli pezzi, quindi deve fare molti scambi inutili fino a quando non viene assegnato l'intero 2 GB. In realtà la ricerca della risposta per input dell'utente è molto più veloce, perché a quel punto il programma non deve andare a chiedere la memoria.
Level River St,

1

Apocalisse 1: C, 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();}

Questo è ancora più affamato di memoria del rev 0. Stesso principio: costruisci una tabella con un valore positivo per tutte le possibili somme di 2 quadrati (e zero per quei numeri che non sono somme di due quadrati), quindi cerca.

In questo giro uso una matrice di shortinvece di charmemorizzare i colpi, così posso memorizzare la radice di una delle coppie di quadrati nella tabella anziché solo una bandiera. Questo semplifica pnotevolmente la funzione (per decodificare la somma di 2 quadrati) in quanto non è necessario un loop.

Windows ha un limite di 2 GB per gli array. Posso aggirare quello con short s[15<<26]cui è un array di 1006632960 elementi, abbastanza per rispettare le specifiche. Sfortunatamente, la dimensione totale del runtime del programma è ancora superiore a 2 GB e (nonostante abbia modificato le impostazioni del sistema operativo) non sono stato in grado di superare queste dimensioni (anche se teoricamente possibile). Il migliore che posso fare è short s[14<<26](939524096 elementi.) m*mDeve essere strettamente inferiore a questo (30651 ^ 2 = 939483801.) Tuttavia, il programma funziona perfettamente e dovrebbe funzionare su qualsiasi sistema operativo che non abbia questa limitazione.

Codice Ungolfed

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.) 

Rev 0 C, 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();}

Questa è una bestia affamata di memoria. Prende un array da 1 GB, calcola tutte le possibili somme di 2 quadrati e memorizza un flag per ciascuno nell'array. Quindi, per l'input dell'utente z, cerca nell'array due somme di 2 quadrati a e za.

la funzione p quindi riconsegna i quadrati originali utilizzati per fare le somme di 2 quadrati ae z-ali stampa, il primo di ogni coppia come numero intero, il secondo come doppio (se devono essere tutti numeri interi sono necessari altri due caratteri, t> m=t.)

Il programma impiega un paio di minuti per costruire la tabella delle somme di quadrati (penso che ciò sia dovuto a problemi di gestione della memoria, vedo che l'allocazione della memoria aumenta lentamente invece di saltare come ci si potrebbe aspettare.) Tuttavia, una volta fatto ciò produce risposte molto rapidamente (se devono essere calcolati più numeri, il programma scanfpuò essere inserito in un ciclo.

codice ungolfed

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)  

Esempio di output

Il primo è per la domanda. Il secondo è stato scelto come difficile da cercare. In questo caso il programma deve cercare fino a 8192 ^ 2 + 8192 ^ 2 = 134217728, ma richiede solo pochi secondi una volta creata la tabella.

123456789
0 2.000000 3328 10601.000000

805306368
8192 8192.000000 8192 24576.000000

Non dovresti aggiungere un prototipo per sqrt?
edc65,

@ edc65 Sto usando il compilatore GCC (che è per Linux, ma ho l'ambiente Cygwin Linux installato sul mio computer Windows.) Ciò significa che non ho bisogno di mettere #include <stdio.h>(per scanf / printf) o #include <math.h>(per sqrt.) Il compilatore collega automaticamente le librerie necessarie. Devo ringraziare Dennis per questo (mi ha detto su questa domanda codegolf.stackexchange.com/a/26330/15599 ) Il miglior consiglio sul golf che abbia mai avuto.
Level River St,

Mi stavo già chiedendo perché Hunt the Wumpus è apparso nelle domande collegate. :) A proposito, non so cosa usi GCC su Windows, ma il linker GNU non collega automaticamente la libreria matematica, con o senza il include. Per compilare su Linux, hai bisogno della bandiera-lm
Dennis,

@Dennis questo è interessante, include stdioe diverse altre librerie, ma non mathancora con il include? Con che capisco se metti il ​​flag del compilatore, non ti serve includecomunque? Beh, funziona per me, quindi non mi lamento, grazie ancora per il suggerimento. A proposito, spero di pubblicare una risposta completamente diversa approfittando del teorema di Legendre (ma userà ancora un sqrt.)
Level River St

-lminfluenza il linker, non il compilatore. gccsceglie di non richiedere i prototipi per le funzioni che "conosce", quindi funziona con o senza le inclusioni. Tuttavia, i file header forniscono solo prototipi di funzioni, non le funzioni stesse. Su Linux (ma non su Windows, a quanto pare), la libreria matematica libm non fa parte delle librerie standard, quindi devi istruire lda collegarti ad essa.
Dennis,

1

Mathematica, 138 caratteri

Quindi si scopre che questo produce risultati negativi e immaginari per determinati input, come sottolineato da edc65 (ad esempio, 805306368), quindi questa non è una soluzione valida. Lo lascerò per ora, e forse, se odio davvero il mio tempo, tornerò e proverò a sistemarlo.

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}]

Oppure, non richiesto:

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}
]

Non ho guardato troppo gli algoritmi, ma mi aspetto che sia la stessa idea. Ho appena trovato la soluzione ovvia e l'ho modificata fino a quando non ha funzionato. L'ho provato per tutti i numeri tra 1 e 1 miliardo e ... funziona. Il test richiede solo circa 100 secondi sulla mia macchina.

Il bello di questo è che, poiché b, c e d sono definiti con assegnazioni ritardate, :=non devono essere ridefiniti quando viene diminuito a. Ciò ha salvato alcune righe extra che avevo prima. Potrei golf ulteriormente e nidificare le parti ridondanti, ma ecco la prima bozza.

Oh, e lo esegui come S@123456789e puoi provarlo con {S@#, Total[(S@#)^2]} & @ 123456789o # == Total[(S@#)^2]&[123456789]. Il test esaustivo è

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

Ho usato un'istruzione Print [] prima, ma questo ha rallentato molto, anche se non viene mai chiamato. Vai a capire.


Questo è davvero pulito! Sono sorpreso che basti semplicemente prendere ogni valore tranne il primo il più grande possibile. Per il golf, è probabilmente più breve salvare n - a^2 - b^2 - c^2come variabile e verificare che sia d^2uguale.
xnor

2
Funziona davvero? Quale soluzione trova per l'ingresso 805306368?
edc65,

S [805306368] = {- 28383, 536 I, 32 I, I}. Huh. Che fa produce 805.306.368 quando riassumere, ma ovviamente c'è un problema con questo algoritmo. Immagino che dovrò ritirarlo per ora; grazie per
averlo

2
I numeri che falliscono sembrano tutti divisibili per grandi potenze di 2. In particolare, sembrano avere la forma a * 4^(2^k)per k>=2, avendo estratto tutti i poteri di 4 in modo che anon sia un multiplo di 4 (ma potrebbe essere pari). Inoltre, ognuno aè 3 mod 4 o due volte tale numero. Il più piccolo è 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]

Forza bruta semplice su quadrati precalcolati.

Ha bisogno del -O dell'opzione di compilazione (ho aggiunto 3 caratteri per questo). Richiede meno di 1 minuto per il caso peggiore 999950883.

Testato solo su GHC.


1

C: 198 caratteri

Posso probabilmente comprimerlo fino a poco più di 100 caratteri. Quello che mi piace di questa soluzione è la minima quantità di spazzatura, solo un semplice for-loop, che fa quello che dovrebbe fare un for-loop (che deve essere pazzo).

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);}

E pesantemente settato:

#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;
}

Modifica: non è abbastanza veloce per tutti gli input, ma tornerò con un'altra soluzione. Lascerò questo disordine di operazione ternaria rimanere per ora.


1

Rev. B: C, 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);}

Grazie a @Dennis per i miglioramenti. Il resto della risposta di seguito non viene aggiornato dalla rev. A.

Rev A: C, 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);}

Molto più veloce della mia altra risposta e con molta meno memoria!

Questo utilizza http://en.wikipedia.org/wiki/Legendre%27s_three-square_theorem . Qualsiasi numero non del seguente modulo può essere espresso come la somma di 3 quadrati (io chiamo questo il modulo proibito):

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

Si noti che tutti i numeri quadrati dispari sono della forma (8b+1)e tutti i numeri quadrati pari sono superficialmente della forma 4b. Tuttavia, ciò nasconde il fatto che tutti i numeri pari quadrati sono nella forma 4^a*(odd square)==4^a*(8b+1). Di conseguenza2^x-(any square number < 2^(x-1)) per disparix sarà sempre nella forma proibita. Quindi questi numeri e i loro multipli sono casi difficili, motivo per cui così tanti programmi qui dividono i poteri di 4 come primo passo.

Come indicato nella risposta di @ xnor, N-a*anon può essere nella forma proibita per 2 valori consecutivi di a. Di seguito presento una forma semplificata del suo tavolo. Oltre al fatto che dopo la divisione per 4 N%4non può essere uguale a 0, si noti che ci sono solo 2 valori possibili per (a*a)%4.

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

Quindi, vogliamo evitare che i valori di (N-a*a)ciò possano essere della forma proibita, vale a dire quelli in cui (N-a*a)%4è 3 o 0. Come si può vedere, ciò non può accadere per lo stesso Ncon sia dispari che pari(a*a) .

Quindi, il mio algoritmo funziona in questo modo:

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

Mi piace in particolare il modo in cui eseguo il passaggio 3. Aggiungo 3 a N, quindi è necessario il decremento se (3+N-a*a)%4 =3 o 2. (ma non 1 o 0.) Dividilo per 2 e l'intero lavoro può essere fatto con un'espressione abbastanza semplice .

Codice Ungolfed

Nota il singolo forloop qe l'uso di division / modulo per derivare i valori di be cda esso. Ho provato a usare acome divisore invece di 256 per salvare byte, ma a volte il valore di anon era corretto e il programma si bloccava, forse indefinitamente. 256 è stato il miglior compromesso che posso usare >>8invece che /256per la divisione.

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);}

Produzione

Una stranezza interessante è che se si inserisce un numero quadrato, N-(a*a)= 0. Ma il programma rileva che 0%4= 0 e diminuisce al quadrato successivo in basso. Di conseguenza, gli input dei numeri quadrati vengono sempre scomposti in un gruppo di quadrati più piccoli a meno che non siano della forma 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

Sorprendente! 0,003 s per ogni input! È possibile ottenere quei 5 caratteri indietro: 1. Dichiarare m=1prima main. 2. Eseguire scanfnella fordichiarazione. 3. Utilizzare floatinvece di double. 4. n%4<1è più corto di !(n%4). 5. C'è uno spazio obsoleto nella stringa di formato di printf.
Dennis,


Grazie per i suggerimenti! n-=a*anon funziona, perché apuò essere modificato in seguito (fornisce alcune risposte errate e si blocca in un numero limitato di casi, come 100 + 7 = 107). Ho incluso tutto il resto. Sarebbe bello accorciare qualcosa, printfma penso che l'unica risposta sia cambiare la lingua. La chiave per accelerare è accontentarsi arapidamente di un buon valore . Scritto in C e con uno spazio di ricerca inferiore a 256 ^ 2, questo è probabilmente il programma più veloce qui.
Level River St,

Giusto, scusa. Accorciare l' printfaffermazione sembra difficile senza usare una macro o un array, che aggiungerebbe altrove altrove. Cambiare lingua sembra il modo "facile". Il tuo approccio peserebbe 82 byte in CJam.
Dennis,

0

JavaScript - 175 191 176 173 caratteri

Forza bruta, ma veloce.

Modifica Veloce ma non abbastanza per qualche input sgradevole. Ho dovuto aggiungere un primo passo di riduzione moltiplicando per 4.

Modifica 2 Sbarazzarsi della funzione, emettere all'interno del loop, quindi forzare l'uscita dalla contesa

Modifica 3 0 non è un input valido

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='')

Ungolfed:

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 */
    }
  }
}

Esempio di output

123456789
11111,48,10,8

805306368
16384,16384,16384,0
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.