Perl + Math :: {ModInt, Polynomial, Prime :: Util}, คะแนน≤ 92819
$m=Math::Polynomial;sub l{($n,$b,$d)=@_;$n||$d||return;$n%$b,l($n/$b,$b,$d&&$d-1)}sub g{$p=$m->interpolate([grep ref$_[$_],0..$map{$p->evaluate($_)}0..$}sub p{prev_prime(128**$s)}sub e{($_,$r)=@_;length||return'';$s=$r+1;s/^[␀␁]/␁$&/;@l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);$@l+$r>p($s)&&return e($_,$s);$a=0;join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)}sub d{@l=split/([␀-␡]+)/,$_[0];@l||return'';$s=vecmax map length,@l;@l=g map{length==$s&&mod($m->new(map{ord()%128}split//)->evaluate(128),p$s)}@l;$$_=$m->new(map{$_->residue}@l)->evaluate(p$s)->to_bytes;s/^␁//;$_}
รูปภาพควบคุมใช้เพื่อแสดงอักขระควบคุมที่สอดคล้องกัน (เช่น␀
เป็นอักขระ NUL ตัวอักษร) ไม่ต้องกังวลกับการพยายามอ่านรหัส มีรุ่นที่อ่านเพิ่มเติมได้ด้านล่าง
-Mbigint -MMath::ModInt=mod -MMath::Polynomial -MNtheory=:all
ทำงานด้วย -MMath::Bigint=lib,GMP
ไม่จำเป็น (และไม่รวมอยู่ในคะแนน) แต่ถ้าคุณเพิ่มก่อนห้องสมุดอื่น ๆ มันจะทำให้โปรแกรมทำงานได้เร็วขึ้น
การคำนวณคะแนน
อัลกอริทึมที่นี่ค่อนข้างจะแก้ไขไม่ได้ แต่จะค่อนข้างยากที่จะเขียน (เนื่องจาก Perl ไม่ได้มีห้องสมุดที่เหมาะสม) ด้วยเหตุนี้ฉันจึงทำการแลกเปลี่ยนขนาด / ประสิทธิภาพในรหัสบนพื้นฐานที่กำหนดว่าสามารถบันทึกไบต์ในการเข้ารหัสได้จึงไม่มีประเด็นที่จะพยายามกำจัดทุกจุดจากการเล่นกอล์ฟ
โปรแกรมประกอบด้วยรหัส 600 ไบต์บวก 78 โทษสำหรับตัวเลือกบรรทัดคำสั่งให้โทษ 678 จุด ส่วนที่เหลือของคะแนนคำนวณโดยใช้โปรแกรมในกรณีที่ดีที่สุดและกรณีที่เลวร้ายที่สุด (ในแง่ของความยาวเอาท์พุท) สตริงสำหรับทุกความยาวจาก 0 ถึง 99 และทุกระดับรังสีจาก 0 ถึง 9; กรณีเฉลี่ยอยู่ที่ใดที่หนึ่งระหว่างและสิ่งนี้ให้ขอบเขตกับคะแนน (มันไม่คุ้มค่าที่จะพยายามคำนวณค่าที่แน่นอนยกเว้นว่ามีรายการอื่นเข้ามาด้วยคะแนนที่คล้ายกัน)
นี่จึงหมายความว่าคะแนนจากประสิทธิภาพการเข้ารหัสอยู่ในช่วง 91100 ถึง 92141 รวมดังนั้นคะแนนสุดท้ายคือ:
91100 + 600 + 78 = 91778 ≤คะแนน≤ 92819 = 92141 + 600 + 78
เวอร์ชันที่ไม่ค่อยตีกอล์ฟพร้อมความคิดเห็นและรหัสทดสอบ
นี่คือโปรแกรมดั้งเดิม + การขึ้นบรรทัดใหม่การเยื้องและความคิดเห็น (อันที่จริงแล้วเวอร์ชัน golfed ถูกสร้างขึ้นโดยลบบรรทัดใหม่ / การเยื้อง / ความคิดเห็นออกจากเวอร์ชันนี้)
use 5.010; # -M5.010; free
use Math::BigInt lib=>'GMP'; # not necessary, but makes things much faster
use bigint; # -Mbigint
use Math::ModInt 'mod'; # -MMath::ModInt=mod
use Math::Polynomial; # -MMath::Polynomial
use ntheory ':all'; # -Mntheory=:all
use warnings; # for testing; clearly not necessary
### Start of program
$m=Math::Polynomial; # store the module in a variable for golfiness
sub l{ # express a number $n in base $b with at least $d digits, LSdigit first
# Note: we can't use a builtin for this because the builtins I'm aware of
# assume that $b fits into an integer, which is not necessarily the case.
($n,$b,$d)=@_;
$n||$d||return;
$n%$b,l($n/$b,$b,$d&&$d-1)
}
sub g{ # replaces garbled blocks in the input with their actual values
# The basic idea here is to interpolate a polynomial through all the blocks,
# of the lowest possible degree. Unknown blocks then get the value that the
# polynomial evaluates to. (This is a special case of Reed-Solomon coding.)
# Clearly, if we have at least as many ungarbled blocks as we did original
# elements, we'll get the same polynomial, thus we can always reconstruct
# the input.
# Note (because it's confusing): @_ is the input, $_ is the current element
# in a loop, but @_ is written as $_ when using the [ or # operator (e.g.
# $_[0] is the first element of @_.
# We waste a few bytes of source for efficiency, storing the polynomial
# in a variable rather than recalculating it each time.
$p=$m->interpolate([grep ref$_[$_],0..$#_],[grep ref,@_]);
# Then we just evaluate the polynomial for each element of the input.
map{$p->evaluate($_)}0..$#_
}
sub p{ # determines maximum value of a block, given (radiation+1)
# We split the input up into blocks. Each block has a prime number of
# possibilities, and is stored using the top 7 bits of (radiation+1)
# consecutive bytes of the output. Work out the largest possible prime that
# satisfies this property.
prev_prime(128**$s)
}
sub e{ # encoder; arguments: input (bytestring), radiation (integer)
($_,$r)=@_; # Read the arguments into variables, $_ and $r respectively
length||return''; # special case for empty string
$s=$r+1; # Also store radiation+1; we use it a lot
# Ensure that the input doesn't start with NUL, via prepending SOH to it if
# it starts with NUL or SOH. This means that it can be converted to a number
# and back, roundtripping correctly.
s/^[␀␁]/␁$&/; #/# <- unconfuse Stack Exchange's syntax highlighting
# Convert the input to a bignum, then to digits in base p$s, to split it
# into blocks.
@l=map{mod($_,p$s)}l(Math::BigInt->from_bytes($_),p$s);
# Encoding can reuse code from decoding; we append $r "garbled blocks" to
# the blocks representing the input, and run the decoder, to figure out what
# values they should have.
$#l+=$r;
# Our degarbling algorithm can only handle at most p$s blocks in total. If
# that isn't the case, try a higher $r (which will cause a huge increase in
# $b and a reduction in @l).
@l+$r>p($s)&&return e($_,$s);
# Convert each block to a sequence of $s digits in base 128, adding 128 to
# alternating blocks; this way, deleting up to $r (i.e. less than $s) bytes
# will preserve the boundaries between each block; then convert that to a
# string
$a=0; # we must initialize $a to make this function deterministic
join'',map{map{chr$_+$a}l($_->residue,128,$s,($a^=128))}g(@l)
}
sub d{ # decoder: arguments; encdng (bytestring)
# Reconstruct the original blocks by looking at their top bits
@l=split/([␀-␡]+)/,$_[0];
@l||return''; # special case for empty string
# The length of the longest block is the radiation parameter plus 1 (i.e.
# $s). Use that to reconstruct the value of $s.
$s=vecmax map length,@l;
# Convert each block to a number, or to undef if it has the wrong length.
# Then work out the values for the undefs.
@l=g map{
# Convert blocks with the wrong length to undef.
length==$s&&
# Convert other blocks to numbers, via removing any +128 and then
# using Math::Polynomial to convert the digit list to a number.
mod($m->new(map{ord()%128}split// #/# <- fix syntax highlighting
)->evaluate(128),p$s)
}@l;
# Remove the redundant elements at the end; now that they've reconstructed
# the garbled elements they have no further use.
$#l-=$s-1;
# Convert @l to a single number (reversing the conversion into blocks.)
$_=$m->new(map{$_->residue}@l)->evaluate(p$s)
# Convert that number into a string.
->to_bytes;
# Delete a leading SOH.
s/^␁//; #/# <- unconfuse Stack Exchange's syntax highlighting
# Finally, return the string.
$_
}
### Testing code
use Encode qw/encode decode/;
# Express a string using control pictures + IBM437, to make binary strings
# easier for a human to parse
sub format_string {
($_)=@_;
$_ = decode("Latin-1", $_);
s/[\0-\x1f]/chr (0x2400 + ord $&)/eag;
s/\x7f/chr 0x2421/eag;
s/[ -~\x80-\xff]/decode("IBM437",$&)/eag;
encode("UTF-8","\x{ff62}$_\x{ff63}")
}
sub test {
my ($string, $radiation, $samples) = @_;
say "Input: ", format_string($string);
my $encoding = e($string, $radiation);
say "Encoding: ", format_string($encoding);
say "Input length ", length($string), ", encoding length ", length($encoding), ", radiation $radiation";
my $decoding = d($encoding);
$decoding eq $string or die "Mistake in output!";
say "Decoding: ", format_string($decoding), " from ",
format_string($encoding);
# Pseudo-randomly generate $samples radiation-damaged versions.
srand 1;
for my $i (1..$samples) {
my $encdng = $encoding;
for my $r (1..$radiation) {
substr $encdng, int(rand(length $encdng)), 1, "";
}
my $newdecoding = d($encdng);
say "Decoding: ", format_string($newdecoding), " from ",
format_string($encdng);
$newdecoding eq $string or die "Mistake in output!";
}
say "";
length $encoding;
}
test "abcdefghijklm", 1, 10;
test "abcdefghijklm", 2, 10;
test "abcdefghijklm", 5, 10;
test "abcdefghijklm", 10, 10;
test "\0\0\0\0\0", 1, 10;
test "\5\4\3\2\1", 2, 10;
test "a", 10, 10;
my %minlength = ();
my %maxlength = ();
for my $length (0..99) {
my ($min, $max) = ("", "");
$length and ($min, $max) =
("\2" . "\0" x ($length - 1), "\1" . "\377" x ($length - 1));
for my $radiation (0..9) {
$minlength{"$length-$radiation"} = test $min, $radiation, 1;
$maxlength{"$length-$radiation"} = test $max, $radiation, 1;
}
}
say "Minimum score: ", vecsum values %minlength;
say "Maximum score: ", vecsum values %maxlength;
ขั้นตอนวิธี
ลดความซับซ้อนของปัญหา
แนวคิดพื้นฐานคือการลดปัญหา "การเข้ารหัสการลบ" (ซึ่งไม่ใช่การสำรวจอย่างกว้างขวาง) ไปสู่ปัญหาการลบโค้ด (การสำรวจพื้นที่ทางคณิตศาสตร์อย่างครอบคลุม) แนวคิดเบื้องหลังการเข้ารหัสการลบคือคุณกำลังเตรียมข้อมูลที่จะส่งผ่าน "ช่องทางลบ" ซึ่งเป็นช่องที่บางครั้งแทนที่อักขระที่ส่งด้วยอักขระ "garble" ที่ระบุตำแหน่งที่ทราบข้อผิดพลาด (กล่าวอีกนัยหนึ่งก็ชัดเจนว่าเกิดการทุจริตแม้ว่าตัวละครดั้งเดิมยังไม่เป็นที่รู้จัก) ความคิดที่อยู่เบื้องหลังนั้นค่อนข้างง่าย: เราแบ่งการป้อนข้อมูลลงในบล็อกของความยาว ( รังสี+ 1) และใช้เจ็ดในแปดบิตในแต่ละบล็อกสำหรับข้อมูลในขณะที่บิตที่เหลือ (ในโครงสร้างนี้ MSB) จะสลับระหว่างการตั้งค่าสำหรับทั้งบล็อกชัดเจนสำหรับบล็อกถัดไปทั้งหมดตั้งสำหรับบล็อก หลังจากนั้นเป็นต้น เนื่องจากบล็อกมีความยาวมากกว่าพารามิเตอร์การแผ่รังสีอย่างน้อยหนึ่งอักขระจากแต่ละบล็อกจึงยังคงอยู่ในเอาต์พุต ดังนั้นด้วยการวิ่งของตัวละครที่มี MSB เดียวกันเราสามารถหาว่าบล็อกไหนเป็นตัวละครแต่ละตัว จำนวนบล็อกยังมากกว่าพารามิเตอร์การแผ่รังสีดังนั้นเราจึงมีบล็อกที่ไม่เสียหายอย่างน้อยหนึ่งครั้งใน encdng; เราจึงรู้ว่าบล็อคทั้งหมดที่ยาวที่สุดหรือถูกมัดไว้นานที่สุดนั้นไม่เสียหายทำให้เราสามารถรักษาบล็อคสั้น ๆ ที่เสียหายได้ (ซึ่งเป็น Garble) เราสามารถอนุมานพารามิเตอร์รังสีเช่นนี้ (มัน '
ลบการเข้ารหัส
ในส่วนของการลบโค้ดของปัญหาสิ่งนี้ใช้กรณีพิเศษแบบง่าย ๆ ของโครงสร้าง Reed-Solomon นี่คือการก่อสร้างอย่างเป็นระบบ: ผลลัพธ์ (อัลกอริธึมการเข้ารหัสลบข้อมูล) เท่ากับอินพุตบวกจำนวนบล็อกเพิ่มเติมเท่ากับพารามิเตอร์รังสี เราสามารถคำนวณค่าที่จำเป็นสำหรับบล็อกเหล่านี้ในวิธีที่ง่าย (และเล่นกอล์ฟ!) ผ่านการปฏิบัติต่อพวกมันในรูปแบบที่ยุ่งเหยิงจากนั้นรันอัลกอริธึมการถอดรหัสกับพวกมันเพื่อ
ความคิดที่แท้จริงที่อยู่เบื้องหลังการก่อสร้างนั้นง่ายมาก: เราพอดีกับพหุนามซึ่งเป็นระดับขั้นต่ำสุดที่เป็นไปได้กับบล็อกทั้งหมดในการเข้ารหัส ถ้าพหุนามเป็นfบล็อกแรกคือf (0) ส่วนที่สองคือf (1) และอื่น ๆ เป็นที่ชัดเจนว่าระดับของพหุนามจะเท่ากับจำนวนบล็อกของอินพุตลบ 1 (เพราะเราใส่พหุนามให้พอดีกับอันดับแรกจากนั้นใช้มันเพื่อสร้างบล็อค "เช็ค" พิเศษ) และเนื่องจากd 1 จุดที่ไม่ซ้ำกันกำหนดพหุนามของการศึกษาระดับปริญญาdการบิดเบือนจำนวนบล็อกใด ๆ (ขึ้นอยู่กับพารามิเตอร์การแผ่รังสี) จะทำให้จำนวนบล็อกที่ไม่เสียหายเท่ากับอินพุตดั้งเดิมซึ่งเป็นข้อมูลเพียงพอที่จะสร้างพหุนามเดียวกันขึ้นมาใหม่ (จากนั้นเราต้องประเมินพหุนามเพื่อคลายบล็อกหนึ่งบล็อก)
การแปลงฐาน
การพิจารณาขั้นสุดท้ายที่เหลืออยู่ที่นี่จะเกี่ยวกับมูลค่าที่แท้จริงของบล็อก ถ้าเราทำการแก้ไขพหุนามในจำนวนเต็มผลลัพธ์อาจเป็นจำนวนตรรกยะ (แทนที่จะเป็นจำนวนเต็ม) มีขนาดใหญ่กว่าค่าอินพุตหรือไม่พึงประสงค์ ดังนั้นแทนที่จะใช้จำนวนเต็มเราใช้สนาม จำกัด ในโปรแกรมนี้ฟิลด์ จำกัด ที่ใช้คือฟิลด์ของจำนวนเต็มโมดูโลpโดยที่pเป็นไพร์มที่ใหญ่ที่สุดที่น้อยกว่า 128 รังสี +1(เช่นนายกที่ใหญ่ที่สุดที่เราสามารถใส่จำนวนค่าที่แตกต่างกันเท่ากับนายกนั้นลงในส่วนข้อมูลของบล็อก) ข้อได้เปรียบที่สำคัญของฟิลด์ จำกัด คือการแบ่ง (ยกเว้นด้วย 0) ถูกกำหนดไว้อย่างไม่ซ้ำกันและจะสร้างมูลค่าภายในฟิลด์นั้นเสมอ ดังนั้นค่าที่ถูกแก้ไขของพหุนามจะพอดีกับบล็อกในลักษณะเดียวกับที่อินพุตทำ
ในการแปลงอินพุตเป็นชุดข้อมูลบล็อกจากนั้นเราต้องทำการแปลงฐาน: แปลงอินพุตจากฐาน 256 เป็นตัวเลขจากนั้นแปลงเป็นฐานp (เช่นสำหรับพารามิเตอร์รังสีที่ 1 เรามีp= 16381) นี่ส่วนใหญ่จัดขึ้นโดย Perl ของการขาดการแปลงขั้นพื้นฐาน (คณิตศาสตร์ :: Prime :: Util มีบางอย่าง แต่พวกเขาไม่ทำงานสำหรับฐาน bignum และบางช่วงเวลาที่เราทำงานด้วยที่นี่มีขนาดใหญ่อย่างไม่น่าเชื่อ) เนื่องจากเรากำลังใช้ Math :: Polynomial สำหรับการแก้ไขพหุนามอยู่แล้วฉันจึงสามารถนำมันกลับมาใช้ใหม่เป็นฟังก์ชั่น "แปลงจากตัวเลขหลัก" (ผ่านการดูตัวเลขเป็นค่าสัมประสิทธิ์ของพหุนามและประเมินผล) ได้ดี แม้ว่าจะไปทางอื่นฉันต้องเขียนฟังก์ชั่นด้วยตัวเอง โชคดีที่มันไม่ยากเกินกว่าที่จะเขียน น่าเสียดายที่การแปลงฐานนี้หมายความว่าโดยทั่วไปการแสดงผลจะไม่สามารถอ่านได้ นอกจากนี้ยังมีปัญหากับศูนย์นำ
ควรสังเกตว่าเราไม่สามารถมีบล็อกมากกว่าpบล็อกในเอาต์พุต (มิฉะนั้นดัชนีของบล็อกสองบล็อกจะเท่ากันและอาจจำเป็นต้องสร้างผลลัพธ์ที่แตกต่างจากพหุนาม) สิ่งนี้จะเกิดขึ้นเมื่ออินพุตมีขนาดใหญ่มากเท่านั้น โปรแกรมนี้จะช่วยแก้ปัญหาในวิธีที่ง่ายมาก: การเพิ่มการฉายรังสี (ซึ่งจะทำให้บล็อกขนาดใหญ่และหน้ามีขนาดใหญ่มากหมายถึงเราสามารถใส่ข้อมูลที่มากขึ้นในการและที่ชัดเจนนำไปสู่ผลที่ถูกต้อง)
อีกจุดหนึ่งที่น่าทำคือเราเข้ารหัสสตริง null ให้ตัวเองเพราะโปรแกรมที่เขียนจะขัดข้องเป็นอย่างอื่น เป็นการเข้ารหัสที่ดีที่สุดอย่างชัดเจนและไม่ว่าพารามิเตอร์การแผ่รังสีจะเป็นอย่างไร
การปรับปรุงที่มีศักยภาพ
ความไร้ประสิทธิภาพหลักของซีมโทติคในโปรแกรมนี้คือการใช้โมดูโล - ไพรม์เป็นฟิลด์ จำกัด ในคำถาม มีขอบเขตเขตข้อมูลขนาด 2 nอยู่ (ซึ่งเป็นสิ่งที่เราต้องการตรงนี้เพราะขนาดของส่วนบรรจุของบล็อกนั้นเป็นพลังของ 128) น่าเสียดายที่มันค่อนข้างซับซ้อนกว่าการสร้างโมดูโลแบบธรรมดาซึ่งหมายความว่า Math :: ModInt จะไม่ตัดมัน (และฉันไม่สามารถหาห้องสมุดใด ๆ ใน CPAN สำหรับการจัดการฟิลด์ที่มีขนาดไม่ใหญ่มาก) ฉันต้องเขียนทั้งคลาสด้วยคณิตศาสตร์ที่มากเกินไปสำหรับ Math :: Polynomial เพื่อให้สามารถจัดการกับมันได้และ ณ จุดนั้นค่าใช้จ่ายไบต์อาจสูงเกินกว่าการสูญเสีย (น้อยมาก) จากการใช้เช่น 16381 มากกว่า 16384
ข้อดีอีกอย่างของการใช้ขนาด power-of-2 ก็คือการแปลงฐานจะง่ายขึ้นมาก อย่างไรก็ตามในทั้งสองกรณีวิธีที่ดีกว่าในการแสดงความยาวของอินพุตจะมีประโยชน์ วิธี "การเพิ่ม 1 ในกรณีที่ไม่ชัดเจน" นั้นง่าย แต่สิ้นเปลือง การแปลงฐาน Bijective เป็นหนึ่งในวิธีที่เป็นไปได้ที่นี่ (แนวคิดคือคุณมีฐานเป็นตัวเลขและ 0 ไม่ใช่ตัวเลขดังนั้นแต่ละหมายเลขจะสอดคล้องกับสตริงเดียว)
แม้ว่าประสิทธิภาพของการเข้ารหัสของ asymptotic นี้จะดีมาก (เช่นสำหรับอินพุตของความยาว 99 และพารามิเตอร์การแผ่รังสีของ 3, การเข้ารหัสนั้นมีความยาว 128 ไบต์เสมอ, แทนที่จะเป็น ~ 400 ไบต์ที่ใช้วิธีการทำซ้ำ ๆ ) ไม่ดีในการป้อนข้อมูลสั้น ๆ ความยาวของการเข้ารหัสเป็นอย่างน้อยกำลังสองของพารามิเตอร์การแผ่รังสี + 1 ดังนั้นสำหรับอินพุตที่สั้นมาก (ความยาว 1 ถึง 8) ที่รังสี 9 ความยาวของเอาต์พุตคือ 100 อย่างไรก็ตาม (ที่ความยาว 9 ความยาวของเอาต์พุตเป็นบางครั้ง 100 และบางครั้ง 110. ) วิธีการที่ใช้การทำซ้ำอย่างชัดเจนเอาชนะการลบนี้ - วิธีการเข้ารหัสตามปัจจัยการผลิตที่มีขนาดเล็กมาก; มันอาจจะคุ้มค่าที่จะเปลี่ยนระหว่างอัลกอริทึมหลายตัวตามขนาดของอินพุต
ในที่สุดมันไม่ได้เกิดขึ้นจริงในการให้คะแนน แต่มีพารามิเตอร์การแผ่รังสีที่สูงมากการใช้บิตของทุกไบต์ (⅛ของขนาดเอาต์พุต) เพื่อกำหนดขอบเขตบล็อกนั้นสิ้นเปลือง มันจะถูกกว่าหากใช้ตัวคั่นระหว่างบล็อกแทน การสร้างบล็อคใหม่จากตัวคั่นค่อนข้างยากกว่าด้วยวิธีการสลับ MSB แต่ฉันเชื่อว่ามันเป็นไปได้อย่างน้อยถ้าข้อมูลมีความยาวเพียงพอ (ด้วยข้อมูลสั้น ๆ มันอาจยากที่จะอนุมานพารามิเตอร์รังสีจากเอาต์พุต) . นั่นจะเป็นสิ่งที่ต้องพิจารณาหากมีการเล็งหาแนวทางในอุดมคติแบบไม่ต้องคำนึงถึงพารามิเตอร์โดยไม่คำนึงถึงพารามิเตอร์
(และแน่นอนอาจมีอัลกอริทึมที่แตกต่างอย่างสิ้นเชิงที่ให้ผลลัพธ์ที่ดีกว่าอันนี้!)