Perl - 116 ไบต์ 87 ไบต์ (ดูอัปเดตด้านล่าง)
#!perl -p
$.<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$.}($j++)x4;$n&&redo}
$_="@a"
นับ Shebang เป็นหนึ่งไบต์เพิ่มบรรทัดใหม่เพื่อสุขภาพจิตแนวนอน
บางสิ่งบางอย่างมาจากการรวมรหัสกอล์ฟ เร็วที่สุดรหัสการส่ง
เฉลี่ย (ที่เลวร้ายที่สุด?) ความซับซ้อนกรณีที่ดูเหมือนว่าจะเป็นO (log n) O (n 0.07 ) ไม่มีอะไรที่ฉันได้พบวิ่งช้ากว่า 0.001s และผมได้ตรวจสอบในช่วงที่ทั้งหมดจากการ900000000 - 999999999 หากคุณพบสิ่งใดที่ใช้เวลานานกว่านั้นอย่างมาก ~ 0.1 วินาทีหรือมากกว่านั้นโปรดแจ้งให้เราทราบ
ตัวอย่างการใช้งาน
$ echo 123456789 | timeit perl four-squares.pl
11110 157 6 2
Elapsed Time: 0:00:00.000
$ echo 1879048192 | timeit perl four-squares.pl
32768 16384 16384 16384
Elapsed Time: 0:00:00.000
$ echo 999950883 | timeit perl four-squares.pl
31621 251 15 4
Elapsed Time: 0:00:00.000
สองขั้นสุดท้ายเหล่านี้ดูเหมือนจะเป็นกรณีที่เลวร้ายที่สุดสำหรับการส่งอื่น ๆ ในทั้งสองกรณีวิธีแก้ปัญหาที่แสดงนั้นเป็นสิ่งแรกที่ได้รับการตรวจสอบ สำหรับ123456789
มันเป็นครั้งที่สอง
หากคุณต้องการทดสอบช่วงของค่าต่างๆคุณสามารถใช้สคริปต์ต่อไปนี้:
use Time::HiRes qw(time);
$t0 = time();
# enter a range, or comma separated list here
for (1..1000000) {
$t1 = time();
$initial = $_;
$j = 0; $i = 1;
$i<<=1,$_>>=2until$_&3;
{$n=$_;@a=map{$n-=$a*($a-=$_%($b=1|($a=0|sqrt$n)>>1));$_/=$b;$a*$i}($j++)x4;$n&&redo}
printf("%d: @a, %f\n", $initial, time()-$t1)
}
printf('total time: %f', time()-$t0);
ดีที่สุดเมื่อไปยังไฟล์ ช่วงนี้1..1000000
ใช้เวลาประมาณ 14 วินาทีบนคอมพิวเตอร์ของฉัน (71,000 ค่าต่อวินาที) และช่วงนั้น999000000..1000000000
ใช้เวลาประมาณ 20 วินาที (50,000 ค่าต่อวินาที) ซึ่งสอดคล้องกับความซับซ้อนเฉลี่ยO (บันทึก n)
ปรับปรุง
แก้ไข : ปรากฎว่าขั้นตอนวิธีนี้จะคล้ายกับหนึ่งที่ได้ถูกนำมาใช้เครื่องคิดเลขจิตสำหรับศตวรรษที่อย่างน้อย
ตั้งแต่เดิมโพสต์ผมได้ตรวจสอบทุกค่าในช่วงตั้งแต่1..1000000000 พฤติกรรม 'กรณีที่เลวร้ายที่สุด' ถูกแสดงด้วยค่า699731569ซึ่งทดสอบรวมทั้งหมด190ชุดก่อนที่จะถึงวิธีแก้ปัญหา หากคุณพิจารณา190จะเป็นค่าคงที่ขนาดเล็ก - และแน่นอนฉันทำ - พฤติกรรมกรณีที่เลวร้ายในช่วงที่จำเป็นต้องได้รับการพิจารณาO (1) นั่นคือเร็วเท่าที่ค้นหาวิธีแก้ปัญหาจากตารางยักษ์และโดยเฉลี่ยอาจเร็วกว่า
อีกสิ่งหนึ่งที่แม้ว่า หลังจาก190การทำซ้ำแล้วสิ่งที่มีขนาดใหญ่กว่า1,444,00ยังไม่ได้ทำมันเกินกว่าผ่านครั้งแรก ตรรกะสำหรับการสำรวจเส้นทางแรกที่ไม่มีค่า - มันไม่ได้ใช้ โค้ดด้านบนสามารถย่อให้สั้นลงได้เล็กน้อย:
#!perl -p
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
$_="@a"
ซึ่งจะทำการส่งผ่านครั้งแรกของการค้นหาเท่านั้น เราจำเป็นต้องยืนยันว่าไม่มีค่าต่ำกว่า144400ที่จำเป็นต้องผ่านรอบที่สองแม้ว่า:
for (1..144400) {
$initial = $_;
# reset defaults
$.=1;$j=undef;$==60;
$.*=2,$_/=4until$_&3;
@a=map{$=-=$%*($%=$=**.5-$_);$%*$.}$j++,(0)x3while$=&&=$_;
# make sure the answer is correct
$t=0; $t+=$_*$_ for @a;
$t == $initial or die("answer for $initial invalid: @a");
}
กล่าวโดยย่อคือช่วง1..1000000000มีวิธีแก้ปัญหาเวลาเกือบคงที่และคุณกำลังดูอยู่
อัพเดทแล้ว
@Dennisและฉันได้ทำการปรับปรุงหลายขั้นตอนวิธีนี้ คุณสามารถติดตามความคืบหน้าในความคิดเห็นด้านล่างและการสนทนาที่ตามมาหากคุณสนใจ จำนวนเฉลี่ยของการวนซ้ำสำหรับช่วงที่ต้องการลดลงจากเพียง4ลงเหลือ1.229และเวลาที่ใช้ในการทดสอบค่าทั้งหมดสำหรับ1..1000000000ได้รับการปรับปรุงจาก 18m 54s เป็น 2m 41s กรณีที่เลวร้ายที่สุดต้องมีการวนซ้ำ190ครั้ง; กรณีที่เลวร้ายที่สุดในขณะนี้, 854382778 , ต้องการเพียง21
รหัส Python สุดท้ายคือต่อไปนี้:
from math import sqrt
# the following two tables can, and should be pre-computed
qqr_144 = set([ 0, 1, 2, 4, 5, 8, 9, 10, 13,
16, 17, 18, 20, 25, 26, 29, 32, 34,
36, 37, 40, 41, 45, 49, 50, 52, 53,
56, 58, 61, 64, 65, 68, 72, 73, 74,
77, 80, 81, 82, 85, 88, 89, 90, 97,
98, 100, 101, 104, 106, 109, 112, 113, 116,
117, 121, 122, 125, 128, 130, 133, 136, 137])
# 10kb, should fit entirely in L1 cache
Db = []
for r in range(72):
S = bytearray(144)
for n in range(144):
c = r
while True:
v = n - c * c
if v%144 in qqr_144: break
if r - c >= 12: c = r; break
c -= 1
S[n] = r - c
Db.append(S)
qr_720 = set([ 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121,
144, 145, 160, 169, 180, 196, 225, 241, 244, 256, 265, 289,
304, 324, 340, 361, 369, 385, 400, 409, 436, 441, 481, 484,
496, 505, 529, 544, 576, 580, 585, 601, 625, 640, 649, 676])
# 253kb, just barely fits in L2 of most modern processors
Dc = []
for r in range(360):
S = bytearray(720)
for n in range(720):
c = r
while True:
v = n - c * c
if v%720 in qr_720: break
if r - c >= 48: c = r; break
c -= 1
S[n] = r - c
Dc.append(S)
def four_squares(n):
k = 1
while not n&3:
n >>= 2; k <<= 1
odd = n&1
n <<= odd
a = int(sqrt(n))
n -= a * a
while True:
b = int(sqrt(n))
b -= Db[b%72][n%144]
v = n - b * b
c = int(sqrt(v))
c -= Dc[c%360][v%720]
if c >= 0:
v -= c * c
d = int(sqrt(v))
if v == d * d: break
n += (a<<1) - 1
a -= 1
if odd:
if (a^b)&1:
if (a^c)&1:
b, c, d = d, b, c
else:
b, c = c, b
a, b, c, d = (a+b)>>1, (a-b)>>1, (c+d)>>1, (c-d)>>1
a *= k; b *= k; c *= k; d *= k
return a, b, c, d
สิ่งนี้ใช้ตารางการแก้ไขที่คำนวณล่วงหน้าสองตารางขนาด 10kb หนึ่งตารางและอีก 253kb รหัสข้างต้นรวมถึงฟังก์ชั่นเครื่องกำเนิดสำหรับตารางเหล่านี้แม้ว่าสิ่งเหล่านี้อาจจะต้องคำนวณในเวลารวบรวม
รุ่นที่มีตารางการแก้ไขที่มีขนาดเล็กกว่าสามารถดูได้ที่นี่: http://codepad.org/1ebJC2OVเวอร์ชันนี้ต้องใช้การเฉลี่ย1.620การทำซ้ำต่อเทอมโดยมีกรณีที่เลวร้ายที่สุดที่38และช่วงทั้งหมดทำงานในประมาณ 21 วินาที เวลาเล็กน้อยถูกสร้างขึ้นโดยใช้ bitwise and
สำหรับการแก้ไขbแทนโมดูโล
ปรับปรุง
แม้ค่ามีแนวโน้มที่จะสร้างโซลูชันมากกว่าค่าคี่
บทความการคำนวณทางจิตที่เชื่อมโยงกับบันทึกก่อนหน้านี้ว่าหากหลังจากลบปัจจัยทั้งหมดของสี่ค่าที่จะถูกย่อยสลายเป็นเลขคู่ค่านี้สามารถแบ่งได้สองค่าและวิธีการสร้างใหม่:
แม้ว่าสิ่งนี้อาจสมเหตุสมผลสำหรับการคำนวณทางจิต (ค่าที่น้อยกว่ามักจะคำนวณได้ง่ายกว่า) แต่ก็ไม่สมเหตุสมผลนัก หากคุณใช้256สุ่ม4 -tuples และตรวจสอบผลรวมของสแควร์สโมดูโล8คุณจะพบว่าค่า1 , 3 , 5และ7มีค่าเฉลี่ย32ครั้ง อย่างไรก็ตามค่า2และ6มีค่าถึง48ครั้ง การคูณค่าคี่ด้วย2จะพบวิธีแก้ปัญหาโดยเฉลี่ยน้อยกว่า33%การทำซ้ำ การฟื้นฟูมีดังต่อไปนี้:
ต้องใช้ความระมัดระวังว่าaและbมีความเท่าเทียมกันเท่ากันเช่นเดียวกับcและdแต่หากพบวิธีการแก้ปัญหาทั้งหมดคำสั่งที่เหมาะสมจะรับประกันว่าจะมีอยู่จริง
ไม่จำเป็นต้องตรวจสอบเส้นทางที่เป็นไปไม่ได้
หลังจากเลือกค่าที่สองแล้วbอาจเป็นไปไม่ได้เลยที่วิธีแก้ปัญหาจะมีอยู่เนื่องจากมีสารตกค้างกำลังสองที่เป็นไปได้สำหรับโมดูโลที่กำหนด แทนที่จะทำการตรวจสอบต่อไปหรือย้ายไปที่การวนซ้ำครั้งถัดไปค่าของbสามารถ 'แก้ไข' โดยการลดค่าลงด้วยจำนวนที่น้อยที่สุดซึ่งอาจนำไปสู่การแก้ปัญหา สองตารางการแก้ไขเก็บค่าเหล่านี้หนึ่งสำหรับข , และอื่น ๆ สำหรับค การใช้โมดูโล่ที่สูงขึ้น (แม่นยำยิ่งขึ้นการใช้โมดูโล่ที่มีเศษกำลังสองน้อยกว่า) จะส่งผลให้การปรับปรุงดีขึ้น ค่าaไม่ต้องการการแก้ไขใด ๆ โดยการแก้ไขnให้เป็นค่าทั้งหมดของaถูกต้อง