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]
ขอแสดงถึงการดำเนินงานของการได้รับจำนวนนี้โดยi
l@d
แม้ว่าวิธีที่ดีที่สุดในการรับรายการเหล่านี้คือการลองชุดค่าผสมและการนับทั้งหมดเราสามารถทำสิ่งนี้ได้อย่างอิสระสำหรับตัวอักษรและตัวเลขดูที่26^4+10^3
กรณีและปัญหาแทนที่จะเป็น26^4*10^3
กรณี แต่เราสามารถทำได้ดีกว่า: l
เป็นเพียงรายการของสัมประสิทธิ์ของ
(x+x^2+...+x^26)^k
ที่เป็นจำนวนตัวอักษรที่นี่k
4
ในทำนองเดียวกันเราได้รับหมายเลขของวิธีการที่จะได้รับผลรวมของตัวเลขในการทำงานของตัวเลขเป็นค่าสัมประสิทธิ์ของ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
และเพียงแค่ทำให้มันจำเป็นต้องมีการส่งออกจำนวนแผ่นป้ายทะเบียนที่สมบูรณ์แบบสำหรับ