Bốn hình vuông với nhau


19

Định lý bốn hình vuông của Lagrange cho chúng ta biết bất kỳ số tự nhiên nào cũng có thể được biểu diễn dưới dạng tổng của bốn số vuông. Nhiệm vụ của bạn là viết một chương trình thực hiện điều này.

Đầu vào: Một số tự nhiên (dưới 1 tỷ)

Đầu ra: Bốn số có bình phương tổng bằng số đó (thứ tự không quan trọng)

Lưu ý: Bạn không phải thực hiện tìm kiếm vũ phu! Chi tiết tại đâytại đây . Nếu có một chức năng tầm thường hóa vấn đề này (tôi sẽ xác định), thì nó không được phép. Chức năng nguyên tố tự động và căn bậc hai được cho phép. Nếu có nhiều hơn một đại diện thì bất kỳ ai cũng được. Nếu bạn chọn thực hiện vũ phu, nó phải chạy trong thời gian hợp lý (3 phút)

đầu vào mẫu

123456789

đầu ra mẫu (hoặc là tốt)

10601 3328 2 0
10601 3328 2

Tôi có thể làm vũ phu mặc dù nếu nó làm cho mã của tôi ngắn hơn?
Martin Ender

@ m.buettner Có, nhưng nó sẽ xử lý số lượng lớn
qwr

@ m.buettner Đọc bài đăng, bất kỳ số tự nhiên nào dưới 1 tỷ
qwr

Ah xin lỗi đã bỏ qua điều đó.
Martin Ender

2
@Dennis Số tự nhiên trong trường hợp này không bao gồm 0.
qwr

Câu trả lời:


1

CJam, 50 byte

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

Câu trả lời thứ ba (và cuối cùng, tôi hứa). Cách tiếp cận này dựa nhiều vào câu trả lời của primo .

Hãy thử trực tuyến trong trình thông dịch CJam .

Sử dụng

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

Lý lịch

  1. Sau khi thấy thuật toán cập nhật của Primo , tôi phải xem cách triển khai của CJam:

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

    Chỉ có 58 byte! Thuật toán này được thực hiện trong thời gian gần như không đổi và không thể hiện nhiều biến thể cho các giá trị khác nhau của N. Hãy thay đổi ...

  2. Thay vì bắt đầu floor(sqrt(N))và giảm dần, chúng ta có thể bắt đầu 1và tăng dần. Điều này tiết kiệm 4 byte.

    li{W):W;:N4md!}g;0_{;)__*N\-[{_mqi__*@\-}3*])}g+2W#f*p
    
  3. Thay vì biểu thị Nnhư 4**a * b, chúng ta có thể biểu thị nó như p**(2a) * b- đâu plà thừa số nguyên tố nhỏ nhất của N- để tiết kiệm thêm 1 byte.

    li_mF0=~2/#:J_*/:N!_{;)__*N\-[{_mqi__*@\-}3*])}g+Jf*p
    
  4. Việc sửa đổi trước đó cho phép chúng ta một chút thay đổi việc thực hiện (mà không cần chạm các thuật toán riêng của mình): Thay vì phân chia Nbởi p**(2a)và nhân các giải pháp bằng cách p**a, chúng ta có thể trực tiếp hạn chế giải pháp khả thi để bội số của p**a. Điều này tiết kiệm thêm 2 byte.

    li:NmF0=~2/#:J!_{;J+__*N\-[{_mqJ/iJ*__*@\-}3*])}g+`
    
  5. Không giới hạn số nguyên đầu tiên cho bội số p**alưu một byte bổ sung.

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

Thuật toán cuối cùng

  1. Tìm abnhư vậy N = p**(2a) * b, nơi bkhông phải là bội số của p**2plà thừa số nguyên tố nhỏ nhất của N.

  2. Đặt j = p**a.

  3. Đặt k = floor(sqrt(N - j**2) / A) * A.

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

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

  6. Nếu N - j**2 - k**2 - l**2 - m**2 > 0, đặt j = j + 1và quay lại bước 3.

Điều này có thể được thực hiện như sau:

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

Điểm chuẩn

Tôi đã chạy tất cả 5 phiên bản trên tất cả các số nguyên dương lên tới 999.999.999 trên Intel Core i7-3770 của tôi, đo thời gian thực hiện và đếm số lần lặp cần thiết để tìm giải pháp.

Bảng sau đây cho thấy số lần lặp và thời gian thực hiện trung bình cho một số nguyên duy nhất:

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. Chỉ với 4 lần lặp và 6,6 micro giây trên mỗi số nguyên, thuật toán của primo cực kỳ nhanh.

  2. Bắt đầu floor(sqrt(N))có ý nghĩa hơn, vì điều này để lại cho chúng ta các giá trị nhỏ hơn cho tổng của ba hình vuông còn lại. Như mong đợi, bắt đầu từ 1 chậm hơn rất nhiều.

  3. Đây là một ví dụ cổ điển về một ý tưởng tốt được thực hiện tồi. Để thực sự giảm kích thước mã, chúng tôi dựa vào mF, yếu tố này là số nguyên N. Mặc dù phiên bản 3 yêu cầu số lần lặp ít hơn phiên bản 2, nhưng thực tế nó chậm hơn rất nhiều.

  4. Mặc dù thuật toán không thay đổi, phiên bản 4 chậm hơn rất nhiều. Điều này là do nó thực hiện một phép chia dấu phẩy động bổ sung và phép nhân số nguyên trong mỗi lần lặp.

  5. Đối với đầu vào N = p**(2a) ** b, thuật toán 5 sẽ yêu cầu các (k - 1) * p**a + 1lần lặp, trong đó ksố lượng thuật toán lặp 4 yêu cầu. Nếu k = 1hoặc a = 0, điều này làm cho không có sự khác biệt.

    Tuy nhiên, bất kỳ đầu vào của biểu mẫu 4**a * (4**c * (8 * d + 7) + 1)có thể thực hiện khá tệ. Đối với giá trị bắt đầu j = p**a, N - 4**a = 4**(a + c) * (8 * d + 7)nên nó không thể được thể hiện dưới dạng một khoản ba hình vuông. Vì vậy, k > 1và ít nhất là p**alặp đi lặp lại được yêu cầu.

    Rất may, thuật toán gốc của Primo cực kỳ nhanh và N < 1,000,000,000. Trường hợp xấu nhất tôi có thể tìm thấy bằng tay là 265,289,728 = 4**10 * (4**1 * (7 * 8 + 7) + 1), đòi hỏi 6.145 lần lặp. Thời gian thực hiện ít hơn 300 ms trên máy của tôi. Trung bình, phiên bản này chậm hơn 13,5 lần so với triển khai thuật toán của primo.


"Thay vì thể hiện Nnhư 4**a * b, chúng ta có thể diễn đạt nó như p**(2a) * b." Đây thực sự là một cải tiến . Tôi muốn có bao gồm điều này, nhưng nó đã dài hơn rất nhiều (lý tưởng là tìm ra yếu tố hình vuông hoàn hảo lớn nhất). "Bắt đầu với 1 và tăng dần tiết kiệm 4 byte." Điều này chắc chắn là chậm hơn. Thời gian chạy cho bất kỳ phạm vi nhất định là 4-5 lần dài. "Tất cả các số nguyên dương lên tới 999.999.999 mất 24,67 giờ, cho thời gian thực hiện trung bình là 0,0888 mili giây trên mỗi số nguyên." Perl chỉ mất 2,5 giờ để hoàn thành toàn bộ phạm vi và bản dịch Python nhanh hơn gấp 10 lần;)
primo

@primo: Vâng, bạn nói đúng. Chia theo p**alà một cải tiến, nhưng đó là một cải tiến nhỏ. Chia theo hệ số vuông hoàn hảo lớn nhất tạo ra sự khác biệt lớn khi bắt đầu từ 1; nó vẫn là một sự cải tiến khi bắt đầu từ phần nguyên của căn bậc hai. Việc thực hiện nó sẽ chỉ tốn thêm hai byte. Thời gian thực hiện quá nhiều dường như là do sự không cải thiện của tôi, không phải là CJam. Tôi sẽ chạy lại các bài kiểm tra cho tất cả các thuật toán (bao gồm cả thuật toán bạn đề xuất), đếm số lần lặp thay vì đo thời gian trên tường. Hãy xem bao lâu mất ...
Dennis

Tìm hệ số vuông lớn nhất chỉ tốn 2 byte?! Những loại ma thuật này là?
primo

@primo: Nếu số nguyên nằm trên ngăn xếp, 1\hoán đổi nó với 1 (bộ tích lũy), mFđẩy hệ số của nó và {~2/#*}/tăng mọi thừa số nguyên tố lên số mũ của nó chia cho hai, sau đó nhân nó với bộ tích lũy. Để thực hiện trực tiếp thuật toán của bạn, điều đó chỉ thêm 2 byte. Sự khác biệt nhỏ là chủ yếu do cách vụng về tôi đã phải tìm số mũ 4, kể từ CJam không (dường như) có một trong khi vòng lặp ...
Dennis

Dù sao, điểm chuẩn đã hoàn thành. Tổng số lần lặp được yêu cầu để tính hệ số cho tất cả 1.000.000 số nguyên mà không tìm thấy hệ số bình phương lớn nhất là 4.004.829.417, với thời gian thực hiện là 1,83 giờ. Chia theo hệ số vuông lớn nhất làm giảm số lần lặp xuống còn 3.996.724.799, nhưng nó tăng thời gian lên 6,7 giờ. Có vẻ như việc nhân cách hóa tốn nhiều thời gian hơn nhiều so với việc tìm ra các hình vuông ...
Dennis

7

FRACTRAN: 156 98 phân số

Vì đây là một vấn đề lý thuyết số cổ điển, cách nào tốt hơn để giải quyết vấn đề này hơn là sử dụng số!

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

Mất trong đầu vào của các hình thức 2 n × 193 và đầu ra 3 một × 5 b × 7 c × 11 d . Có thể chạy trong 3 phút nếu bạn có một thông dịch viên thực sự giỏi. Có lẽ.

... Được thôi, không hẳn vậy. Đây dường như là một vấn đề thú vị khi làm ở FRACTRAN mà tôi phải thử. Rõ ràng, đây không phải là một giải pháp thích hợp cho câu hỏi vì nó không đưa ra các yêu cầu về thời gian (đó là lực lượng vũ phu) và nó thậm chí không được đánh gôn, nhưng tôi nghĩ rằng tôi sẽ đăng bài này ở đây bởi vì đó không phải là câu hỏi của Codegolf mỗi ngày có thể được thực hiện trong FRACTRAN;)

Dấu

Mã này tương đương với giả Python sau:

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

Toán học 61 66 51

Ba phương pháp được hiển thị. Chỉ có cách tiếp cận đầu tiên đáp ứng yêu cầu về thời gian.


1-FindInstance (51 char)

Điều này trả về một giải pháp duy nhất phương trình.

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

Ví dụ và thời gian

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

Điều này cũng hoạt động, nhưng quá chậm để đáp ứng yêu cầu tốc độ.

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

Range[0, Floor@Sqrt@n]^2là tập hợp tất cả các hình vuông nhỏ hơn căn bậc hai của n(hình vuông lớn nhất có thể có trong phân vùng).

{4}yêu cầu các phân vùng nguyên ngồm 4 phần tử từ tập hợp các ô vuông đã đề cập ở trên.

1, trong hàm IntegerPartitionstrả về giải pháp đầu tiên.

[[1]]loại bỏ các niềng răng bên ngoài; giải pháp được trả về như một tập hợp của một phần tử.


f[123456]

{348, 44, 20, 4}


3-PowerRepftimeations

PowerRepftimeations trả về tất cả các giải pháp cho vấn đề 4 hình vuông. Nó cũng có thể giải quyết cho các khoản tiền của các quyền lực khác.

PowersRepftimeations trả lại, trong vòng dưới 5 giây, 181 cách để thể hiện 123456789 dưới dạng tổng của 4 hình vuông:

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

hát

Tuy nhiên, nó là quá chậm đối với các khoản tiền khác.


Wow, Mathematica làm lực lượng vũ phu nhanh chóng. IntegerPartitions có làm điều gì đó thông minh hơn nhiều so với thử mọi kết hợp, như tích chập DFT trên các tập hợp không? Nhân tiện, thông số kỹ thuật yêu cầu các con số, không phải hình vuông của chúng.
xnor

Tôi nghĩ Mathicala sử dụng vũ lực, nhưng có lẽ đã tối ưu hóa IntegerPartitions. Như bạn có thể thấy từ thời gian, tốc độ thay đổi rất nhiều tùy thuộc vào việc số đầu tiên (lớn nhất) có gần với căn bậc hai của hay không n. Cảm ơn đã bắt vi phạm spec trong phiên bản trước.
DavidC

Bạn có thể điểm chuẩn f[805306368]? Không chia cho lũy thừa 4 trước, giải pháp của tôi mất 0,05 giây cho 999999999; Tôi đã hủy bỏ điểm chuẩn cho 805306368 sau 5 phút ...
Dennis

f[805306368]trở lại {16384, 16384, 16384}sau 21 phút. Tôi đã sử dụng {3} thay cho {4}. Nỗ lực giải quyết nó với tổng 4 hình vuông khác không đã không thành công sau vài giờ chạy.
DavidC

Tôi không có quyền truy cập vào Mathicala, nhưng từ những gì tôi đã đọc trong trung tâm tài liệu, IntegerPartitions[n,4,Range[Floor@Sqrt@n]^2cũng sẽ hoạt động tốt. Tuy nhiên, tôi không nghĩ bạn nên sử dụng phương pháp 1 cho điểm của mình, vì nó không tuân thủ giới hạn thời gian được chỉ định trong câu hỏi.
Dennis

7

Perl - 116 byte 87 byte (xem cập nhật bên dưới)

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

Đếm shebang là một byte, các dòng mới được thêm vào cho độ ngang.

Một cái gì đó của một kết hợp .

Độ phức tạp trường hợp trung bình (tệ nhất?) Dường như là O (log n) O (n 0,07 ) . Không có gì tôi tìm thấy chạy chậm hơn 0,001 giây và tôi đã kiểm tra toàn bộ phạm vi từ 900000000 - 999999999 . Nếu bạn tìm thấy bất cứ điều gì mất nhiều thời gian hơn thế, ~ 0,1s trở lên, xin vui lòng cho tôi biết.


Sử dụng mẫu

$ 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

Hai trong số cuối cùng có vẻ là trường hợp xấu nhất cho các bài nộp khác. Trong cả hai trường hợp, giải pháp hiển thị hoàn toàn theo nghĩa đen là điều đầu tiên được kiểm tra. Đối với 123456789, đó là thứ hai.

Nếu bạn muốn kiểm tra một loạt các giá trị, bạn có thể sử dụng tập lệnh sau:

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

Tốt nhất khi được dẫn đến một tập tin. Phạm vi 1..1000000mất khoảng 14 giây trên máy tính của tôi (71000 giá trị mỗi giây) và phạm vi 999000000..1000000000mất khoảng 20 giây (50000 giá trị mỗi giây), phù hợp với độ phức tạp trung bình O (log n) .


Cập nhật

Chỉnh sửa : Hóa ra thuật toán này rất giống với thuật toán đã được sử dụng bởi các máy tính tinh thần trong ít nhất một thế kỷ .

Kể từ khi đăng bài ban đầu, tôi đã kiểm tra mọi giá trị trong phạm vi từ 1..1000000000 . Hành vi 'trường hợp xấu nhất' được thể hiện bằng giá trị 699731569 , đã thử nghiệm tổng cộng 190 kết hợp trước khi đưa ra giải pháp. Nếu bạn coi 190 là một hằng số nhỏ - và tôi chắc chắn làm được - hành vi xấu nhất trong phạm vi yêu cầu có thể được coi là O (1) . Đó là, nhanh như tìm kiếm giải pháp từ một cái bàn khổng lồ, và trung bình, hoàn toàn có thể nhanh hơn.

Một điều khác mặc dù. Sau 190 lần lặp, bất cứ điều gì lớn hơn 144400 thậm chí còn không vượt qua được lần đầu tiên. Logic cho giao dịch theo chiều rộng đầu tiên là vô giá trị - nó thậm chí không được sử dụng. Đoạn mã trên có thể được rút ngắn khá nhiều:

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

Mà chỉ thực hiện vượt qua đầu tiên của tìm kiếm. Tuy nhiên, chúng tôi cần xác nhận rằng không có bất kỳ giá trị nào dưới 144400 cần vượt qua lần thứ hai:

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

Nói tóm lại, trong phạm vi 1..1000000000 , tồn tại một giải pháp thời gian gần như không đổi và bạn đang xem xét nó.


Cập nhật cập nhật

@Dennis và tôi đã thực hiện một số cải tiến thuật toán này. Bạn có thể theo dõi tiến trình trong các bình luận bên dưới và thảo luận tiếp theo, nếu điều đó làm bạn quan tâm. Số lần lặp trung bình cho phạm vi yêu cầu đã giảm từ chỉ hơn 4 xuống còn 1.229 và thời gian cần thiết để kiểm tra tất cả các giá trị cho 1..1000000000 đã được cải thiện từ 18m 54s, xuống còn 2m 41s. Trường hợp xấu nhất trước đây yêu cầu 190 lần lặp; trường hợp xấu nhất bây giờ, 854382778 , chỉ cần 21 .

Mã Python cuối cùng là như sau:

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

Điều này sử dụng hai bảng hiệu chỉnh được tính toán trước, một kích thước 10kb, còn lại là 253kb. Mã ở trên bao gồm các hàm tạo cho các bảng này, mặc dù chúng có thể được tính toán tại thời điểm biên dịch.

Có thể tìm thấy phiên bản với các bảng hiệu chỉnh có kích thước khiêm tốn hơn tại đây: http://codepad.org/1ebJC2OV Phiên bản này yêu cầu trung bình 1.620 lần lặp mỗi thuật ngữ, trong trường hợp xấu nhất là 38 và toàn bộ phạm vi chạy trong khoảng 3 m 21. Một chút thời gian được tạo ra, bằng cách sử dụng bitwise andđể hiệu chỉnh b , thay vì modulo.


Cải tiến

Ngay cả các giá trị có nhiều khả năng tạo ra một giải pháp hơn các giá trị lẻ.
Bài báo tính toán tinh thần được liên kết với các ghi chú trước đó rằng, sau khi loại bỏ tất cả các yếu tố của bốn, giá trị được phân tách là chẵn, giá trị này có thể được chia cho hai và giải pháp được xây dựng lại:

Mặc dù điều này có thể có ý nghĩa cho tính toán tinh thần (các giá trị nhỏ hơn có xu hướng dễ tính toán hơn), nhưng nó không có ý nghĩa nhiều về mặt thuật toán. Nếu bạn lấy 256 ngẫu nhiên 4 lần và kiểm tra tổng của bình phương modulo 8 , bạn sẽ thấy rằng các giá trị 1 , 3 , 57 đều đạt trung bình 32 lần. Tuy nhiên, mỗi giá trị 26 đều đạt tới 48 lần. Nhân các giá trị lẻ với 2 sẽ tìm ra một giải pháp, trung bình, trong các lần lặp ít hơn 33% . Việc xây dựng lại như sau:

Cần phải cẩn thận rằng ab có cùng tính chẵn lẻ, cũng như cd , nhưng nếu một giải pháp được tìm thấy ở tất cả, một trật tự thích hợp được đảm bảo tồn tại.

Đường dẫn không thể không cần phải được kiểm tra.
Sau khi chọn giá trị thứ hai, b , có thể đã không thể tồn tại một giải pháp, với các dư lượng bậc hai có thể có cho bất kỳ modulo nào. Thay vì kiểm tra bằng cách nào, hoặc chuyển sang lần lặp tiếp theo, giá trị của b có thể được 'sửa chữa' bằng cách giảm nó bằng số tiền nhỏ nhất có thể dẫn đến một giải pháp. Hai bảng hiệu chỉnh lưu trữ các giá trị này, một cho b và một cho c . Sử dụng một modulo cao hơn (chính xác hơn, sử dụng một modulo có dư lượng bậc hai tương đối ít hơn) sẽ dẫn đến một sự cải thiện tốt hơn. Giá trị a không cần bất kỳ sự điều chỉnh nào; bằng cách sửa đổi n thành chẵn, tất cả các giá trị củaa là hợp lệ.


1
Điều này thật phi thường! Thuật toán cuối cùng có lẽ là câu trả lời đơn giản nhất trong tất cả các câu trả lời, nhưng 190 lần lặp là tất cả những gì nó cần ...
Dennis

@Dennis Tôi sẽ rất ngạc nhiên nếu nó không được đề cập ở nơi khác. Có vẻ như quá đơn giản để được nhìn quá mức.
primo

1. Tôi tò mò: Có bất kỳ giá trị thử nghiệm nào trong phân tích độ phức tạp của bạn yêu cầu truyền tải đầu tiên không? 2. Bài viết Wikipedia mà bạn liên kết đến hơi khó hiểu. Nó đề cập đến thuật toán Rabin-Shallit, nhưng cung cấp một ví dụ cho một thuật toán hoàn toàn khác. 3. Thật thú vị khi thấy chính xác thuật toán Rabin-Shallit sẽ tốt hơn thuật toán của bạn, tôi tưởng tượng các bài kiểm tra nguyên thủy khá tốn kém trong thực tế.
Dennis

1. Không phải một. 2. Đây là nơi tôi có được thông tin của mình (tức là thuật toán này tồn tại); Tôi chưa thấy phân tích, hoặc thậm chí đọc bài báo. 3. Đường cong trở nên rất dốc vào khoảng 1e60, đến mức thực sự sẽ không quan trọng đến mức độ "chậm" của chữ O (log²n) , nó vẫn sẽ giao nhau vào thời điểm đó.
primo

1
Liên kết thứ hai trong câu hỏi giải thích cách triển khai Rabin-Shallit, nhưng nó không nói về sự phức tạp. Câu trả lời này trên MathOverflow đưa ra một bản tóm tắt hay của bài báo. Nhân tiện, bạn đã khám phá lại một thuật toán được sử dụng bởi Gottfried Ruckle vào năm 1911 ( liên kết ).
Dennis

6

Con trăn 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])

Sau khi chúng ta giảm đầu vào Nkhông chia hết cho 4, nó phải có thể biểu thị dưới dạng tổng của bốn hình vuông trong đó một trong số chúng là giá trị lớn nhất có thể a=int(N**0.5)hoặc nhỏ hơn một, chỉ còn lại một phần nhỏ cho tổng ba hình vuông khác chăm sóc. Điều này làm giảm đáng kể không gian tìm kiếm.

Đây là một bằng chứng sau này mã này luôn tìm thấy một giải pháp. Chúng tôi muốn tìm một avì vậy đó n-a^2là tổng của ba hình vuông. Từ Định lý ba hình vuông của Legendre , một số là tổng của ba hình vuông trừ khi nó là hình thức 4^j(8*k+7). Cụ thể, những con số này là 0 hoặc 3 (modulo 4).

Chúng tôi cho thấy rằng không có hai số liên tiếp acó thể làm cho số tiền còn lại N-a^2có hình dạng như vậy cho cả hai giá trị liên tiếp .. Chúng tôi có thể làm như vậy bằng cách tạo một bảng aNmodulo 4, lưu ý rằng N%4!=0vì chúng tôi đã trích xuất tất cả các lũy thừa của 4 trong số đó N.

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

Bởi vì không có hai lần acho liên tiếp (N-a*a)%4 in [0,3], một trong số chúng là an toàn để sử dụng. Vì vậy, chúng tôi tham lam sử dụng lớn nhất có thể nvới n^2<=N, và n-1. Vì N<(n+1)^2, phần còn lại N-a^2được biểu diễn dưới dạng tổng của ba hình vuông là nhiều nhất (n+1)^2 -(n-1)^2, bằng nhau 4*n. Vì vậy, nó chỉ đủ để kiểm tra các giá trị tối đa 2*sqrt(n), chính xác là phạm vi R.

Người ta có thể tối ưu hóa thêm thời gian chạy bằng cách dừng lại sau một giải pháp duy nhất, tính toán thay vì lặp lại giá trị cuối cùng dvà chỉ tìm kiếm giữa các giá trị b<=c<=d. Nhưng, ngay cả khi không có những tối ưu hóa này, trường hợp xấu nhất tôi có thể tìm thấy đã hoàn thành sau 45 giây trên máy của mình.

Chuỗi "for x in R" là không may. Nó có thể được rút ngắn bằng cách thay thế hoặc thay thế chuỗi bằng cách lặp qua một chỉ mục duy nhất mã hóa (a, b, c, d). Nhập itertools hóa ra không đáng.

Chỉnh sửa: Thay đổi int(2*n**.5)+1từ 2*int(n**.5)+2để làm cho đối số sạch hơn, cùng số lượng ký tự.


Điều này không hiệu quả với tôi ...5 => (2, 1, 0, 0)
Harry Beadle

Thật kỳ lạ, nó hoạt động với tôi: Tôi 5 => (2, 1, 0, 0)chạy trên Ideone 3.2.3 hoặc trong Idle 3.2.2. Bạn được những gì?
xnor

1
@xnor BritishColour được 5 => (2, 1, 0, 0). Bạn thậm chí đã đọc bình luận? (Bây giờ chúng tôi có 3 bình luận liên tiếp có đoạn mã đó. Chúng tôi có thể giữ chuỗi đó không?)
Justin

@Quincunx Nếu chúng ta giải mã 5 => (2, 1, 0, 0), điều đó có nghĩa 2^2 + 1^2 + 0^2 + 0^2 = 5. Vì vậy, vâng, chúng tôi có thể.
Bác sĩ Rebmu

1
Quincunx, tôi đã đọc bình luận của @ BritishColour, và theo như tôi thấy, 5 => (2, 1, 0, 0)là chính xác. Các ví dụ trong câu hỏi coi 0 ^ 2 = 0 là số vuông hợp lệ. Vì vậy, tôi đã giải thích (như tôi nghĩ xnor đã làm) rằng British Color có một thứ khác. Màu sắc của Anh, như bạn không trả lời lại, chúng tôi có thể cho rằng bạn thực tế có được 2,1,0,0không?
Cấp sông St

5

CJam , 91 90 74 71 byte

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

Nhỏ gọn, nhưng chậm hơn so với cách tiếp cận khác của tôi.

Hãy thử trực tuyến! Dán , nhập số nguyên mong muốn vào Đầu vào và bấm Chạy .

Lý lịch

Bài đăng này bắt đầu như một câu trả lời GolfScript 99 byte . Mặc dù vẫn còn chỗ để cải thiện, GolfScript thiếu chức năng sqrt tích hợp. Tôi đã giữ phiên bản GolfScript cho đến phiên bản 5 , vì nó rất giống với phiên bản CJam.

Tuy nhiên, việc tối ưu hóa kể từ phiên bản 6 yêu cầu các nhà khai thác không có sẵn trong GolfScript, vì vậy thay vì đăng các giải thích riêng cho cả hai ngôn ngữ, tôi đã quyết định bỏ phiên bản ít cạnh tranh hơn (và chậm hơn nhiều).

Thuật toán được thực hiện tính toán các số theo lực lượng vũ phu:

  1. Đối với đầu vào m, tìm NWnhư vậy m = 4**W * N.

  2. Đặt i = 257**2 * floor(sqrt(N/4)).

  3. Đặt i = i + 1.

  4. Tìm số nguyên j, k, lsao cho i = 257**2 * j + 257 * k + l, ở đâu k, l < 257.

  5. Kiểm tra nếu d = N - j**2 - k**2 - l**2là một hình vuông hoàn hảo.

  6. Nếu không, và quay lại bước 3.

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

Ví dụ

$ 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

Thời gian tương ứng với Intel Core i7-4700MQ.

Làm thế nào nó hoạt động

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

Điều này dựa trên thuật toán trên trang Wikipedia, đây là một lực lượng vũ phu 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>`

Nhanh, nhưng dài. Dòng mới có thể được gỡ bỏ.

Hãy thử trực tuyến. Lưu ý rằng trình thông dịch trực tuyến có giới hạn thời gian 5 giây, vì vậy nó có thể không hoạt động cho tất cả các số.

Lý lịch

Thuật toán tận dụng định lý ba hình vuông của Legendre , nói rằng mọi số tự nhiên n không phải là dạng

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

có thể được biểu diễn dưới dạng tổng của ba hình vuông.

Thuật toán thực hiện như sau:

  1. Thể hiện số như 4**i * j.

  2. Tìm các số nguyên lớn nhất kk**2 <= jj - k**2thỏa mãn giả thiết của định lý ba vuông Legendre của.

  3. Đặt i = 0.

  4. Kiểm tra nếu j - k**2 - (i / 252)**2 - (i % 252)**2là một hình vuông hoàn hảo.

  5. Nếu không, hãy tăng dần ivà quay lại bước 4.

Ví dụ

$ 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

Thời gian tương ứng với Intel Core i7-4700MQ.

Làm thế nào nó hoạt động

~              # 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
Tôi đã không hiểu j-k-(i/252)-(i%252). Từ ý kiến ​​của bạn (tôi thực sự không thể đọc mã), có vẻ như bạn có ý nghĩa j-k-(i/252)^2-(i%252)^2. BTW, tương đương với j-k-(i/r)^2-(i%r)^2nơi r = sqrt (k) có thể lưu một vài ký tự (và dường như hoạt động mà không gặp vấn đề gì ngay cả với k = 0 trong chương trình C của tôi.)
Level River St

@steveverrill: Vâng, tôi đã phạm sai lầm. Cảm ơn đã chú ý. Nó phải j-k^2-(i/252)^2-(i%252)^2. Tôi vẫn đang chờ OP làm rõ nếu 0 có phải là đầu vào hợp lệ hay không. Chương trình của bạn cung cấp 1414 -nan 6 4.000000cho đầu vào 0.
Dennis

Tôi đang nói về chương trình mới của tôi bằng định lý Legendre, mà tôi chưa đăng. Có vẻ như nó không bao giờ gọi mã bằng% hoặc / khi tôi có giá trị tương đương k = 0, đó là lý do tại sao nó không gây ra vấn đề. Bạn sẽ thấy khi tôi đăng nó. Vui mừng bạn có chương trình cũ của tôi chạy. Bạn đã có bộ nhớ để xây dựng bảng 2GB đầy đủ trong rev 1 chưa, và mất bao lâu?
Cấp sông St

Vâng, trình biên dịch C có thể hoạt động khá bất ngờ khi tối ưu hóa. Trong GolfScript, 0/=> sụp đổ! : P Tôi đã chạy rev 1 trên máy tính xách tay của tôi (i7-4700MQ, RAM 8 GiB). Trung bình, thời gian thực hiện là 18,5 giây.
Dennis

Wow là 18,5 giây bao gồm cả việc xây dựng bảng? Phải mất hơn 2 phút trên máy của tôi. Tôi có thể thấy vấn đề là quản lý bộ nhớ Windows. Thay vì cung cấp cho chương trình 2GB mà nó cần ngay lập tức, nó cung cấp cho chương trình nhỏ, do đó, nó phải thực hiện nhiều thao tác hoán đổi không cần thiết cho đến khi phân bổ 2GB đầy đủ. Trên thực tế, việc tìm kiếm câu trả lời cho mỗi đầu vào của người dùng nhanh hơn nhiều, bởi vì sau đó chương trình không phải đi xin bộ nhớ.
Cấp sông St

1

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

Điều này thậm chí còn nhiều bộ nhớ hơn so với rev 0. Nguyên tắc tương tự: xây dựng một bảng có giá trị dương cho tất cả các tổng có thể có 2 hình vuông (và không cho các số không phải là tổng của hai hình vuông), sau đó tìm kiếm nó.

Trong vòng quay này, sử dụng một mảng shortthay vì charđể lưu trữ các lần truy cập, vì vậy tôi có thể lưu trữ gốc của một trong các cặp hình vuông trong bảng thay vì chỉ là một lá cờ. Điều này đơn giản hóa chức năng p(để giải mã tổng 2 hình vuông) đáng kể vì không cần vòng lặp.

Windows có giới hạn 2GB trên mảng. Tôi có thể làm tròn với short s[15<<26]một mảng gồm 1006632960 phần tử, đủ để tuân thủ thông số kỹ thuật. Thật không may, kích thước tổng thời gian chạy chương trình vẫn là hơn 2GB và (mặc dù giá trị setting của hệ điều hành) tôi đã không thể chạy trên kích thước này (mặc dù nó là về mặt lý thuyết có thể.) Điều tốt nhất tôi có thể làm là short s[14<<26](939.524.096 yếu tố.) m*mPhải hoàn toàn ít hơn mức này (30651 ^ 2 = 939483801.) Tuy nhiên, chương trình chạy hoàn hảo và sẽ hoạt động trên mọi hệ điều hành không có hạn chế này.

Mã bị đánh cắp

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

Đây là một con thú đói ký ức. Nó lấy một mảng 1GB, tính toán tất cả các khoản tiền có thể có của 2 hình vuông và lưu một cờ cho mỗi mảng trong mảng. Sau đó, cho người dùng nhập z, nó tìm kiếm mảng cho hai tổng 2 hình vuông a và za.

các chức năng psau đó reconsitutes các ô vuông ban đầu đã được sử dụng để làm cho số tiền của 2 hình vuông az-avà in chúng, là người đầu tiên của mỗi cặp là một số nguyên, thứ hai như một đôi (nếu nó được tất cả các số nguyên hơn hai nhân vật là cần thiết, t> m=t.)

Chương trình mất vài phút để xây dựng bảng tổng bình phương (tôi nghĩ rằng điều này là do vấn đề quản lý bộ nhớ, tôi thấy việc phân bổ bộ nhớ tăng chậm thay vì nhảy lên như mọi người mong đợi.) Tuy nhiên, một khi đã xong tạo ra các câu trả lời rất nhanh (nếu một vài số được tính toán, chương trình từ đó scanftrở đi có thể được đặt trong một vòng lặp.

mã không mã hóa

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)  

Ví dụ đầu ra

Đầu tiên là mỗi câu hỏi. Thứ hai được chọn là một khó khăn để tìm kiếm. Trong trường hợp này, chương trình phải tìm kiếm tới 8192 ^ 2 + 8192 ^ 2 = 134217728, nhưng chỉ mất vài giây sau khi bảng được tạo.

123456789
0 2.000000 3328 10601.000000

805306368
8192 8192.000000 8192 24576.000000

Bạn không nên thêm một nguyên mẫu cho sqrt?
edc65

@ edc65 Tôi đang sử dụng trình biên dịch GCC (dành cho Linux, nhưng tôi đã cài đặt môi trường Cygwin Linux trên máy Windows của mình.) Điều này có nghĩa là tôi không cần phải đặt #include <stdio.h>(cho scanf / printf) hoặc #include <math.h>(cho sqrt.) liên kết các thư viện cần thiết tự động. Tôi phải cảm ơn Dennis vì điều đó (anh ấy đã nói với tôi về câu hỏi này codegolf.stackexchange.com/a/26330/15599 ) Mẹo chơi golf tốt nhất tôi từng có.
Cấp sông St

Tôi đã tự hỏi tại sao Hunt the Wumpus xuất hiện trong các câu hỏi được liên kết. :) Nhân tiện, tôi không biết GCC sử dụng cái gì trên Windows, nhưng trình liên kết GNU không tự động liên kết thư viện toán học, có hoặc không có include. Để biên dịch trên Linux, bạn cần cờ-lm
Dennis

@Dennis đó là thú vị, nó bao gồm stdiovà một số thư viện khác, nhưng không maththậm chí với những include? Bằng cách đó, tôi hiểu nếu bạn đặt cờ trình biên dịch, includedù sao bạn cũng không cần ? Vâng, nó hoạt động với tôi, vì vậy tôi không phàn nàn, cảm ơn vì tiền boa. BTW Tôi hy vọng sẽ đăng một câu trả lời hoàn toàn khác, tận dụng định lý của Legendre (nhưng nó vẫn sẽ sử dụng một sqrt.)
Level River St

-lmảnh hưởng đến trình liên kết, không phải trình biên dịch. gcckhông yêu cầu các nguyên mẫu cho các chức năng mà nó "biết", do đó, nó hoạt động có hoặc không có bao gồm. Tuy nhiên, các tệp tiêu đề chỉ cung cấp các nguyên mẫu chức năng, không phải các chức năng. Trên Linux (nhưng không phải Windows, rõ ràng), thư viện toán học libm không phải là một phần của các thư viện chuẩn, vì vậy bạn phải hướng dẫn ldliên kết với nó.
Dennis

1

Toán học, 138 ký tự

Vì vậy, nó chỉ ra rằng điều này tạo ra kết quả âm tính và tưởng tượng cho một số đầu vào nhất định như được chỉ ra bởi edc65 (ví dụ: 805306368), vì vậy đây không phải là một giải pháp hợp lệ. Bây giờ tôi sẽ để nó lại và có lẽ, nếu tôi thực sự ghét thời gian của mình, tôi sẽ quay lại và cố gắng sửa nó.

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

Hoặc, chưa hoàn thành:

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

Tôi đã không nhìn quá kỹ vào các thuật toán, nhưng tôi hy vọng đây là ý tưởng tương tự. Tôi chỉ cần đưa ra giải pháp rõ ràng và điều chỉnh nó cho đến khi nó hoạt động. Tôi đã thử nghiệm nó cho tất cả các con số từ 1 đến một tỷ và ... nó hoạt động. Bài kiểm tra chỉ mất khoảng 100 giây trên máy của tôi.

Điểm hay của việc này là, vì b, c và d được xác định bằng các bài tập bị trì hoãn :=, nên chúng không phải được xác định lại khi a bị giảm. Điều này đã lưu một vài dòng tôi có trước đây. Tôi có thể đánh gôn thêm và lồng những phần dư thừa, nhưng đây là bản nháp đầu tiên.

Ồ, và bạn chạy nó như S@123456789và bạn có thể kiểm tra nó bằng {S@#, Total[(S@#)^2]} & @ 123456789hoặc # == Total[(S@#)^2]&[123456789]. Bài kiểm tra toàn diện là

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

Tôi đã sử dụng câu lệnh Print [] trước đó nhưng điều đó đã làm chậm nó đi rất nhiều, mặc dù nó không bao giờ được gọi. Đi hình.


Điều này thực sự sạch sẽ! Tôi ngạc nhiên rằng nó chỉ đơn giản là nhận mọi giá trị, nhưng giá trị đầu tiên càng lớn càng tốt. Đối với việc chơi gôn, có thể ngắn hơn để lưu n - a^2 - b^2 - c^2dưới dạng một biến và kiểm tra xem nó có d^2bằng không.
xnor

2
Nó thực sự hoạt động? Giải pháp nào tìm thấy cho đầu vào 805306368?
edc65

S [805306368] = {- 28383, 536 I, 32 I, I}. Huh. Điều đó không tạo ra 805306368 khi bạn tính tổng, nhưng rõ ràng có vấn đề với thuật toán này. Tôi đoán bây giờ tôi sẽ phải rút lại cái này; cảm ơn vì đã chỉ ra ...
krs013

2
Những con số mà thất bại tất cả dường như là chia quyền hạn lớn 2. Cụ thể, họ dường như có dạng a * 4^(2^k)cho k>=2, sau khi chiết xuất ra tất cả các quyền hạn của 4 do đó akhông phải là một bội số của 4 (nhưng có thể là chẵn). Hơn nữa, mỗi cái alà 3 mod 4, hoặc hai lần một số như vậy. Cái nhỏ nhất là 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]

Lực lượng vũ phu đơn giản trên các ô vuông tính toán trước.

Nó cần -Otùy chọn biên dịch (tôi đã thêm 3 ký tự cho việc này). Mất ít hơn 1 phút cho trường hợp xấu nhất 999950883.

Chỉ được thử nghiệm trên GHC.


1

C: 198 ký tự

Tôi có thể ép nó xuống chỉ còn hơn 100 ký tự. Những gì tôi thích về giải pháp này là số lượng rác tối thiểu, chỉ là một vòng lặp đơn giản, làm những gì một vòng lặp for nên làm (điều này thật điên rồ).

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

Và được hoàn thiện rất nhiều:

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

Chỉnh sửa: Nó không đủ nhanh cho tất cả các đầu vào, nhưng tôi sẽ trở lại với một giải pháp khác. Bây giờ tôi sẽ để mớ hỗn độn hoạt động này.


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

Cảm ơn @Dennis vì những cải tiến. Phần còn lại của câu trả lời dưới đây không được cập nhật từ 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);}

Nhanh hơn nhiều so với câu trả lời khác của tôi và với bộ nhớ ít hơn nhiều!

Điều này sử dụng http://en.wikipedia.org/wiki/Legendre%27s_three-sapes_theorem . Bất kỳ số nào không thuộc dạng sau có thể được biểu thị dưới dạng tổng của 3 hình vuông (tôi gọi đây là dạng bị cấm):

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

Lưu ý rằng tất cả các số vuông lẻ đều có dạng (8b+1)và tất cả các số vuông chẵn là bề ngoài của mẫu 4b. Tuy nhiên, điều này che giấu thực tế là tất cả các số vuông chẵn đều có dạng 4^a*(odd square)==4^a*(8b+1). Kết quả là 2^x-(any square number < 2^(x-1))lẻx sẽ luôn ở dạng bị cấm. Do đó những con số này và bội số của chúng là những trường hợp khó khăn, đó là lý do tại sao rất nhiều chương trình ở đây phân chia quyền hạn của 4 là bước đầu tiên.

Như đã nêu trong câu trả lời của @ xnor, N-a*akhông thể là dạng bị cấm đối với 2 giá trị liên tiếp của a. Dưới đây tôi trình bày một hình thức đơn giản của bảng của mình. Ngoài thực tế là sau khi chia cho 4 N%4không thể bằng 0, lưu ý rằng chỉ có 2 giá trị có thể có (a*a)%4.

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

Vì vậy, chúng tôi muốn tránh các giá trị (N-a*a)có thể thuộc dạng bị cấm, cụ thể là các giá trị (N-a*a)%4là 3 hoặc 0. Như có thể thấy, điều này không thể xảy ra giống nhau Nvới cả số lẻ và số chẵn (a*a).

Vì vậy, thuật toán của tôi hoạt động như thế này:

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

Tôi đặc biệt thích cách tôi thực hiện bước 3. Tôi thêm 3 vào N, để giảm dần nếu (3+N-a*a)%4 =3 hoặc 2. (chứ không phải 1 hoặc 0.) Chia phần này cho 2 và toàn bộ công việc có thể được thực hiện bằng một biểu thức khá đơn giản .

Mã bị đánh cắp

Lưu ý forvòng lặp đơn qvà sử dụng phép chia / modulo để lấy các giá trị của bctừ đó. Tôi đã thử sử dụng anhư một ước số thay vì 256 để lưu byte, nhưng đôi khi giá trị của anó không đúng và chương trình bị treo, có thể là vô thời hạn. 256 là sự thỏa hiệp tốt nhất mà tôi có thể sử dụng >>8thay vì /256cho bộ phận.

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

Đầu ra

Một điều thú vị là nếu bạn nhập số vuông, N-(a*a)= 0. Nhưng chương trình phát hiện ra rằng 0%4= 0 và giảm xuống ô vuông tiếp theo. Kết quả là đầu vào số vuông luôn bị phân rã thành một nhóm các ô vuông nhỏ hơn trừ khi chúng có dạng 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

Kinh ngạc! 0,003 s cho mỗi đầu vào! Bạn có thể lấy lại 5 ký tự đó: 1. Khai báo m=1trước main. 2. Thực thi scanftrong fortuyên bố. 3. Sử dụng floatthay vì double. 4. n%4<1ngắn hơn !(n%4). 5. Có một không gian lỗi thời trong chuỗi định dạng của printf.
Dennis


Cảm ơn vì những lời khuyên! n-=a*akhông hoạt động, bởi vì acó thể được sửa đổi sau đó (nó đưa ra một số câu trả lời sai và treo vào một số ít trường hợp, như 100 + 7 = 107.) Tôi bao gồm tất cả phần còn lại. Sẽ rất tốt nếu có gì đó để rút ngắn printfnhưng tôi nghĩ câu trả lời duy nhất là thay đổi ngôn ngữ. Chìa khóa để tăng tốc là giải quyết một giá trị tốt một acách nhanh chóng. Được viết bằng C và với không gian tìm kiếm dưới 256 ^ 2, đây có lẽ là chương trình nhanh nhất ở đây.
Cấp sông St

Phải, xin lỗi. Việc rút ngắn printfcâu lệnh có vẻ khó khăn mà không sử dụng macro hoặc mảng, điều này sẽ thêm số lượng lớn ở nơi khác. Thay đổi ngôn ngữ có vẻ là cách "dễ dàng". Cách tiếp cận của bạn sẽ nặng 82 byte trong CJam.
Dennis

0

JavaScript - 175 191 176 173 ký tự

Lực lượng vũ phu, nhưng nhanh chóng.

Chỉnh sửa nhanh nhưng không đủ cho một số đầu vào khó chịu. Tôi đã phải thêm một bước giảm đầu tiên với bội số của 4.

Chỉnh sửa 2 Loại bỏ chức năng, đầu ra bên trong vòng lặp, sau đó buộc thoát lệnh

Chỉnh sửa 3 0 không phải là đầu vào hợp lệ

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

Ung dung:

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

Ví dụ đầu ra

123456789
11111,48,10,8

805306368
16384,16384,16384,0
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.