Perl - 116 bytes 87 bytes (veja a atualização abaixo)
#!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 o shebang como um byte, novas linhas foram adicionadas à sanidade horizontal.
Algo como uma combinação de envio de código mais rápido para código-golfe .
A complexidade média (pior?) Do caso parece ser O (log n) O (n 0,07 ) . Nada que encontrei é mais lento que 0,001s e verifiquei todo o intervalo de 900000000 a 999999999 . Se você encontrar algo que demore significativamente mais do que isso, ~ 0,1s ou mais, entre em contato.
Uso da amostra
$ 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
Os dois últimos parecem ser os piores cenários para outros envios. Nos dois casos, a solução mostrada é literalmente a primeira coisa verificada. Pois 123456789
é o segundo.
Se você deseja testar um intervalo de valores, pode usar o seguinte 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);
Melhor quando canalizado para um arquivo. O intervalo 1..1000000
leva cerca de 14s no meu computador (71000 valores por segundo) e o intervalo 999000000..1000000000
leva cerca de 20s (50000 valores por segundo), consistente com a complexidade média de O (log n) .
Atualizar
Edit : Acontece que este algoritmo é muito semelhante ao que tem sido usado por calculadoras mentais há pelo menos um século .
Desde a publicação original, verifiquei todos os valores no intervalo de 1..1000000000 . O comportamento do "pior caso" foi exibido pelo valor 699731569 , que testou um total geral de 190 combinações antes de chegar a uma solução. Se você considera 190 uma pequena constante - e eu certamente considero - o pior comportamento possível no intervalo necessário pode ser considerado O (1) . Ou seja, tão rápido quanto procurar a solução em uma mesa gigante e, em média, possivelmente mais rápido.
Outra coisa, porém. Após 190 iterações, qualquer coisa maior que 144400 nem chegou além da primeira passagem. A lógica do percurso pela primeira vez é inútil - nem sequer é usada. O código acima pode ser bastante reduzido:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
Que realiza apenas a primeira passagem da pesquisa. Precisamos confirmar que não há valores abaixo de 144400 que precisem da segunda passagem, no entanto:
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");
}
Resumindo, para o intervalo de 1 a 1000000000 , existe uma solução de tempo quase constante e você está olhando para ela.
Atualização atualizada
@Dennis e eu fizemos várias melhorias neste algoritmo. Você pode acompanhar o progresso nos comentários abaixo e a discussão subsequente, se isso lhe interessar. O número médio de iterações para o intervalo necessário caiu de pouco mais de 4 para 1,229 , e o tempo necessário para testar todos os valores para 1..1000000000 foi aprimorado de 18m 54s para 2m 41s. O pior caso anteriormente exigia 190 iterações; o pior caso agora, 854382778 , precisa apenas de 21 .
O código final do Python é o seguinte:
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
Isso usa duas tabelas de correção pré-calculadas, uma com 10kb de tamanho e a outra com 253kb. O código acima inclui as funções de gerador para essas tabelas, embora provavelmente devam ser calculadas em tempo de compilação.
Uma versão com tabelas de correção de tamanho mais modesto pode ser encontrada aqui: http://codepad.org/1ebJC2OV Esta versão requer uma média de 1.620 iterações por termo, com o pior caso de 38 , e todo o intervalo é executado em cerca de 3m 21s. Um pouco de tempo é compensado, usando bit and
a bit para correção b , em vez de módulo.
Melhorias
Valores pares têm mais probabilidade de produzir uma solução do que valores ímpares.
O artigo de cálculo mental, vinculado anteriormente, observa que se, após remover todos os fatores de quatro, o valor a ser decomposto for par, esse valor poderá ser dividido por dois e a solução reconstruída:
Embora isso possa fazer sentido para o cálculo mental (valores menores tendem a ser mais fáceis de calcular), não faz muito sentido algoritmicamente. Se você pegar 256 pares aleatórios de 4 e examinar a soma dos quadrados do módulo 8 , verá que os valores 1 , 3 , 5 e 7 são alcançados, em média, 32 vezes. No entanto, os valores 2 e 6 são atingidos 48 vezes. A multiplicação dos valores ímpares por 2 encontrará uma solução, em média, em 33% menos iterações. A reconstrução é a seguinte:
Cuidado deve ser tomado para que um e b têm a mesma paridade, bem como c e d , mas se uma solução foi encontrada em tudo, uma ordenação adequada é garantido que existe.
Caminhos impossíveis não precisam ser verificados.
Depois de selecionar o segundo valor, b , já pode ser impossível a existência de uma solução, dados os possíveis resíduos quadráticos para qualquer módulo. Em vez de verificar de qualquer maneira, ou passar para a próxima iteração, o valor de b pode ser 'corrigido', diminuindo-o pela menor quantidade possível de uma solução. As duas tabelas de correção armazenam esses valores, um para be o outro para c . Usar um módulo mais alto (mais precisamente, usar um módulo com relativamente menos resíduos quadráticos) resultará em uma melhoria melhor. O valor a não precisa de correção; modificando n para ser par, todos os valores dea são válidos.