GAP , 416 ไบต์
จะไม่ชนะขนาดรหัสและห่างจากเวลาคงที่ แต่ใช้คณิตศาสตร์เพื่อเร่งความเร็วให้มากขึ้น!
x:=X(Integers);
z:=CoefficientsOfUnivariatePolynomial;
s:=Size;
f:=function(n)
local r,c,p,d,l,u,t;
t:=0;
for r in [1..Int((n+1)/2)] do
for c in [r..n-r+1] do
l:=z(Sum([1..26],i->x^i)^(n-c));
for p in Partitions(c,r) do
d:=x;
for u in List(p,k->z(Sum([0..9],i->x^i)^k)) do
d:=Sum([2..s(u)],i->u[i]*Value(d,x^(i-1))mod x^s(l));
od;
d:=z(d);
t:=t+Binomial(n-c+1,r)*NrArrangements(p,r)*
Sum([2..s(d)],i->d[i]*l[i]);
od;
od;
od;
return t;
end;
หากต้องการบีบพื้นที่ว่างที่ไม่จำเป็นออกมาและรับหนึ่งบรรทัดด้วย 416 ไบต์ไปที่:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
แล็ปท็อป "รุ่นเก่าที่ออกแบบมาสำหรับ Windows XP" ของฉันสามารถคำนวณได้f(10)ในเวลาไม่ถึงหนึ่งนาที
gap> for i in [2..15] do Print(i,": ",f(i),"\n");od;
2: 18
3: 355
4: 8012
5: 218153
6: 6580075
7: 203255386
8: 6264526999
9: 194290723825
10: 6116413503390
11: 194934846864269
12: 6243848646446924
13: 199935073535438637
14: 6388304296115023687
15: 203727592114009839797
มันทำงานอย่างไร
สมมติว่าในตอนแรกเราแค่ต้องการทราบจำนวนแผ่นป้ายทะเบียนที่สมบูรณ์แบบที่เหมาะสมกับรูปแบบLDDLLDLซึ่งLหมายถึงตัวอักษรและ
Dหมายถึงตัวเลข สมมติว่าเรามีรายการlตัวเลขที่
l[i]ให้จำนวนวิธีที่ตัวอักษรสามารถให้ค่าiและรายการที่คล้ายกันdสำหรับค่าที่เราได้รับจากตัวเลข แล้วจำนวนแผ่นป้ายทะเบียนที่สมบูรณ์แบบที่มีค่าร่วมกันiเป็นเพียง
และเรารับจำนวนทั้งหมดแผ่นป้ายทะเบียนที่สมบูรณ์แบบด้วยรูปแบบของเราได้จากข้อสรุปนี้ไปทั่วl[i]*d[i] ขอแสดงถึงการดำเนินงานของการได้รับจำนวนนี้โดยil@d
แม้ว่าวิธีที่ดีที่สุดในการรับรายการเหล่านี้คือการลองชุดค่าผสมและการนับทั้งหมดเราสามารถทำสิ่งนี้ได้อย่างอิสระสำหรับตัวอักษรและตัวเลขดูที่26^4+10^3กรณีและปัญหาแทนที่จะเป็น26^4*10^3
กรณี แต่เราสามารถทำได้ดีกว่า: lเป็นเพียงรายการของสัมประสิทธิ์ของ
(x+x^2+...+x^26)^kที่เป็นจำนวนตัวอักษรที่นี่k4
ในทำนองเดียวกันเราได้รับหมายเลขของวิธีการที่จะได้รับผลรวมของตัวเลขในการทำงานของตัวเลขเป็นค่าสัมประสิทธิ์ของk (1+x+...+x^9)^kหากมีมากกว่าหนึ่งการทำงานของตัวเลขเราต้องรวมรายการที่เกี่ยวข้องกับการดำเนินการd1#d2ที่ในตำแหน่งที่iมีค่าเป็นผลรวมของทั้งหมดที่d1[i1]*d2[i2] i1*i2=iนี่คือการแปลง Dirichlet ซึ่งเป็นเพียงผลิตภัณฑ์ถ้าเราตีความรายการเป็นค่าสัมประสิทธิ์ของชุด Dirchlet แต่เราได้ใช้มันเป็นชื่อพหุนาม (อนุกรมไฟไนต์) และไม่มีวิธีที่ดีในการตีความการดำเนินการสำหรับพวกเขา ฉันคิดว่าไม่ตรงกันนี้เป็นส่วนหนึ่งของสิ่งที่ทำให้ยากที่จะหาสูตรง่าย ๆ #ลองใช้มันในหลายชื่ออยู่แล้วและใช้สัญกรณ์เดียวกัน มันง่ายในการคำนวณเมื่อตัวถูกดำเนินการตัวเดียวคือเรามีp(x) # x^k = p(x^k). เมื่อรวมกับความจริงที่ว่ามันเป็นไบลิเนียร์นี่เป็นวิธีที่ดี (แต่ไม่มีประสิทธิภาพมาก) ในการคำนวณ
โปรดทราบว่าkตัวอักษรให้คุณค่าสูงสุดไม่เกิน26kในขณะที่ตัวเลขหลักเดียวสามารถให้ค่าของk
9^kดังนั้นเรามักจะได้รับพลังสูงที่ไม่จำเป็นในdพหุนาม เพื่อกำจัดพวกเราสามารถคำนวณโมดูโล่x^(maxlettervalue+1)ได้ นี้จะช่วยให้ความเร็วขนาดใหญ่ขึ้นและแม้ว่าฉันไม่ได้แจ้งให้ทราบล่วงหน้าทันทีแม้จะช่วยให้การเล่นกอล์ฟเพราะตอนนี้เรารู้ว่าระดับของการdไม่ได้เป็นใหญ่แล้วว่าของlที่ง่ายขีด จำกัด Sumบนในขั้นสุดท้าย เราได้ความเร็วที่ดียิ่งขึ้นโดยทำการmodคำนวณในอาร์กิวเมนต์แรกของValue
(ดูความคิดเห็น) และทำการ#คำนวณทั้งหมดในระดับที่ต่ำกว่าจะช่วยเพิ่มความเร็วอย่างไม่น่าเชื่อ แต่เรายังคงพยายามที่จะเป็นคำตอบที่ถูกต้องสำหรับปัญหาการตีกอล์ฟ
ดังนั้นเราจึงได้มีของเราlและและสามารถใช้ในการคำนวณจำนวนแผ่นป้ายทะเบียนที่สมบูรณ์แบบด้วยรูปแบบd นั่นเป็นจำนวนเช่นเดียวกับรูปแบบLDDLLDL LDLLDDLโดยทั่วไปเราสามารถเปลี่ยนลำดับการวิ่งของตัวเลขที่มีความยาวต่างกันตามที่เราต้องการ
NrArrangementsให้ความเป็นไปได้ และในขณะที่จะต้องมีตัวอักษรหนึ่งตัวระหว่างตัวเลขวิ่งตัวอักษรอื่น ๆ จะไม่ได้รับการแก้ไข การBinomialนับความเป็นไปได้เหล่านี้
ตอนนี้มันยังคงวิ่งผ่านวิธีที่เป็นไปได้ทั้งหมดของการมีความยาวของตัวเลขการวิ่ง rวิ่งผ่านจำนวนวิ่งcทั้งหมดผ่านจำนวนตัวเลขทั้งหมดทั้งหมดและpผ่านพาร์ติชันทั้งหมดของcด้วยการ
rสรุป
จำนวนพาร์ติชั่นทั้งหมดที่เราดูนั้นน้อยกว่าจำนวนพาร์n+1ติชั่นสองพาร์ติชั่นและฟังก์ชันพาร์ติชั่นก็จะเติบโตขึ้นเช่น
exp(sqrt(n))กัน ดังนั้นในขณะที่ยังมีวิธีง่าย ๆ ในการปรับปรุงเวลาทำงานโดยการนำผลลัพธ์กลับมาใช้ใหม่ (การทำงานผ่านพาร์ติชันตามลำดับที่แตกต่างกัน) เพื่อการปรับปรุงขั้นพื้นฐานเราจำเป็นต้องหลีกเลี่ยงการดูแต่ละพาร์ติชันแยกกัน
คอมพิวเตอร์มันเร็ว
(p+q)@r = p@r + q@rโปรดสังเกตว่า ด้วยตัวเองสิ่งนี้จะช่วยหลีกเลี่ยงการซ้ำซ้อน แต่เมื่อรวมเข้าด้วยกัน(p+q)#r = p#r + q#rก็หมายความว่าเราสามารถรวมกันได้ด้วยการเติมคำหลายคำที่สอดคล้องกับพาร์ติชั่นที่แตกต่างกัน เราไม่สามารถเพิ่มพวกเขาทั้งหมดได้เพราะเรายังต้องรู้ว่าเราต้องรวมอะไรเข้าด้วยกันซึ่งlเราต้องใช้@ปัจจัยอะไรและปัจจัย#ไหนยังคงเป็นไปได้
ลองรวมพหุนามทั้งหมดที่เกี่ยวข้องกับพาร์ติชั่นกับผลรวมและความยาวเท่ากันและบัญชีสำหรับวิธีการกระจายความยาวของตัวเลข แตกต่างจากสิ่งที่ฉันคาดเดาในความคิดเห็นฉันไม่จำเป็นต้องใส่ใจกับค่าที่ใช้น้อยที่สุดหรือความถี่ที่ใช้ถ้าฉันแน่ใจว่าฉันจะไม่ขยายด้วยค่านั้น
นี่คือรหัส C ++ ของฉัน:
#include<vector>
#include<algorithm>
#include<iostream>
#include<gmpxx.h>
using bignum = mpz_class;
using poly = std::vector<bignum>;
poly mult(const poly &a, const poly &b){
poly res ( a.size()+b.size()-1 );
for(int i=0; i<a.size(); ++i)
for(int j=0; j<b.size(); ++j)
res[i+j]+=a[i]*b[j];
return res;
}
poly extend(const poly &d, const poly &e, int ml, poly &a, int l, int m){
poly res ( 26*ml+1 );
for(int i=1; i<std::min<int>(1+26*ml,e.size()); ++i)
for(int j=1; j<std::min<int>(1+26*ml/i,d.size()); ++j)
res[i*j] += e[i]*d[j];
for(int i=1; i<res.size(); ++i)
res[i]=res[i]*l/m;
if(a.empty())
a = poly { res };
else
for(int i=1; i<a.size(); ++i)
a[i]+=res[i];
return res;
}
bignum f(int n){
std::vector<poly> dp;
poly digits (10,1);
poly dd { 1 };
dp.push_back( dd );
for(int i=1; i<n; ++i){
dd=mult(dd,digits);
int l=1+26*(n-i);
if(dd.size()>l)
dd.resize(l);
dp.push_back(dd);
}
std::vector<std::vector<poly>> a;
a.reserve(n);
a.push_back( std::vector<poly> { poly { 0, 1 } } );
for(int i=1; i<n; ++i)
a.push_back( std::vector<poly> (1+std::min(i,n+i-i)));
for(int m=n-1; m>0; --m){
// std::cout << "m=" << m << "\n";
for(int sum=n-m; sum>=0; --sum)
for(int len=0; len<=std::min(sum,n+1-sum); ++len){
poly d {a[sum][len]} ;
if(!d.empty())
for(int sumn=sum+m, lenn=len+1, e=1;
sumn+lenn-1<=n;
sumn+=m, ++lenn, ++e)
d=extend(d,dp[m],n-sumn,a[sumn][lenn],lenn,e);
}
}
poly let (27,1);
let[0]=0;
poly lp { 1 };
bignum t { 0 };
for(int sum=n-1; sum>0; --sum){
lp=mult(lp,let);
for(int len=1; len<=std::min(sum,n+1-sum); ++len){
poly &a0 = a[sum][len];
bignum s {0};
for(int i=1; i<std::min(a0.size(),lp.size()); ++i)
s+=a0[i]*lp[i];
bignum bin;
mpz_bin_uiui( bin.get_mpz_t(), n-sum+1, len );
t+=bin*s;
}
}
return t;
}
int main(){
int n;
std::cin >> n;
std::cout << f(n) << "\n" ;
}
สิ่งนี้ใช้ไลบรารี GNU MP ใน Debian libgmp-devติดตั้ง รวบรวมกับg++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxxคอมไพล์ด้วยโปรแกรมรับอาร์กิวเมนต์จาก stdin echo 100 | time ./plสำหรับระยะเวลาการใช้งาน
ในตอนท้ายa[sum][length][i]จะช่วยให้หลายวิธีในการที่sum
ตัวเลขในการทำงานสามารถให้จำนวนlength iในระหว่างการคำนวณที่จุดเริ่มต้นของวงจะช่วยให้หลายวิธีที่สามารถทำได้ด้วยตัวเลขที่มากกว่าm mทุกอย่างเริ่มต้นด้วย
a[0][0][1]=1ทุกอย่างเริ่มต้นด้วยโปรดทราบว่านี่เป็นชุดของตัวเลขที่เราต้องการในการคำนวณฟังก์ชันสำหรับค่าที่น้อยลง nดังนั้นที่เกือบจะในเวลาเดียวกันเราสามารถคำนวณค่าทั้งหมดขึ้นอยู่กับ
ไม่มีการเรียกซ้ำดังนั้นเราจึงมีการวนซ้ำหลายระดับที่แน่นอน (ระดับการซ้อนที่ลึกที่สุดคือ 6) แต่ละลูปจะต้องผ่านจำนวนของค่าที่เป็นเชิงเส้นในnกรณีที่แย่ที่สุด ดังนั้นเราต้องการเวลาพหุนามเท่านั้น ถ้าเราดูที่ซ้อนกันiและjวนซ้ำมากขึ้นextendเราจะพบข้อ จำกัด สูงสุดjของแบบฟอร์ม N/iนั่นควรให้ปัจจัยลอการิทึมสำหรับjลูปเท่านั้น วงในสุดf
(กับsumnฯลฯ ) คล้ายกัน โปรดจำไว้ว่าเราคำนวณตัวเลขที่เติบโตอย่างรวดเร็ว
โปรดทราบว่าเราจัดเก็บO(n^3)หมายเลขเหล่านี้
จากการทดลองฉันได้รับผลลัพธ์เหล่านี้บนฮาร์ดแวร์ที่เหมาะสม (i5-4590S):
f(50)ต้องการหนึ่งวินาทีและ 23 MB f(100)ต้องการ 21 วินาทีและ 166 MB f(200)ต้องการ 10 นาทีและ 1.5 GB และf(300)ต้องการหนึ่งชั่วโมงและ 5.6 GB O(n^5)นี้แสดงให้เห็นความซับซ้อนของเวลาดีกว่า
Nและเพียงแค่ทำให้มันจำเป็นต้องมีการส่งออกจำนวนแผ่นป้ายทะเบียนที่สมบูรณ์แบบสำหรับ