GAP , 416 byte
Tidak akan menang pada ukuran kode, dan jauh dari waktu yang konstan, tetapi menggunakan matematika untuk mempercepat banyak!
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;
Untuk memeras ruang kosong yang tidak perlu dan mendapatkan satu baris dengan 416 byte, pipa melalui ini:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Laptop lama "dirancang untuk Windows XP" saya dapat menghitung f(10)
dalam waktu kurang dari satu menit dan melangkah lebih jauh dalam waktu kurang dari satu jam:
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
Bagaimana itu bekerja
Misalkan kita pertama-tama hanya ingin mengetahui jumlah plat nomor yang cocok dengan pola LDDLLDL
, di mana L
menunjukkan suatu huruf dan
D
menunjukkan suatu angka. Asumsikan kita memiliki daftar l
angka sehingga
l[i]
memberikan jumlah cara huruf dapat memberikan nilai i
, dan daftar serupa d
untuk nilai yang kita dapatkan dari angka. Maka jumlah plat nomor sempurna dengan nilai umum i
adalah adil
l[i]*d[i]
, dan kami mendapatkan nomor plat nomor sempurna dengan pola kami dengan menjumlahkan ini semua i
. Mari kita tunjukkan operasi mendapatkan jumlah ini pada l@d
.
Sekarang bahkan jika cara terbaik untuk mendapatkan daftar ini adalah dengan mencoba semua kombinasi dan menghitung, kita dapat melakukan ini secara independen untuk huruf dan digit, melihat 26^4+10^3
case daripada 26^4*10^3
case ketika kita menjalankan semua pelat yang sesuai dengan pola. Tetapi kita dapat melakukan jauh lebih baik: l
hanya daftar koefisien di
(x+x^2+...+x^26)^k
mana k
jumlah huruf, di sini 4
.
Demikian pula, kita mendapatkan jumlah cara untuk mendapatkan jumlah digit dalam rangkaian k
digit sebagai koefisien (1+x+...+x^9)^k
. Jika ada lebih dari satu putaran digit, kita perlu menggabungkan daftar terkait dengan operasi d1#d2
yang pada posisi i
memiliki nilai jumlah dari semua d1[i1]*d2[i2]
tempat i1*i2=i
. Ini adalah konvolusi Dirichlet, yang hanya merupakan produk jika kita menafsirkan daftar sebagai koefisien dari seri Dirchlet. Tapi kami sudah menggunakannya sebagai polinomial (seri daya terbatas), dan tidak ada cara yang baik untuk menafsirkan operasi untuk mereka. Saya pikir ketidakcocokan ini adalah bagian dari apa yang membuatnya sulit untuk menemukan formula sederhana. Mari kita gunakan pada polinomial dan gunakan notasi yang sama #
. Sangat mudah untuk menghitung ketika satu operan adalah monomial: yang kita milikip(x) # x^k = p(x^k)
. Bersamaan dengan fakta bahwa itu bilinear, ini memberikan cara yang bagus (tapi tidak sangat efisien) untuk menghitungnya.
Perhatikan bahwa k
huruf memberikan nilai paling banyak 26k
, sedangkan k
angka tunggal dapat memberikan nilai 9^k
. Jadi kita akan sering mendapatkan kekuatan tinggi yang tidak dibutuhkan dalam d
polinomial. Untuk menghilangkannya, kita dapat menghitung modulo x^(maxlettervalue+1)
. Ini memberi kecepatan besar dan, meskipun saya tidak segera menyadarinya, bahkan membantu bermain golf, karena kita sekarang tahu bahwa derajatnya d
tidak lebih besar daripada tingkat l
, yang menyederhanakan batas atas di final Sum
. Kami mendapatkan kecepatan yang lebih baik dengan melakukan mod
perhitungan pada argumen pertama Value
(lihat komentar), dan melakukan keseluruhan #
perhitungan pada level yang lebih rendah memberikan kecepatan yang luar biasa. Tapi kami masih berusaha menjadi jawaban yang sah untuk masalah golf.
Jadi kita sudah mendapat kami l
dan d
dan dapat menggunakannya untuk menghitung jumlah plat sempurna dengan pola LDDLLDL
. Itu adalah angka yang sama dengan polanya LDLLDDL
. Secara umum, kita dapat mengubah urutan deretan digit dengan panjang berbeda sesuai keinginan,
NrArrangements
memberikan jumlah kemungkinan. Dan sementara harus ada satu huruf di antara deretan angka, surat-surat lainnya tidak tetap. The Binomial
menghitung kemungkinan ini.
Sekarang tinggal menjalankan semua cara yang mungkin untuk memiliki panjang digit angka. r
dijalankan melalui semua jumlah run, c
melalui semua jumlah total digit, dan p
melalui semua partisi c
dengan
r
puncak.
Jumlah total partisi yang kita lihat adalah dua kurang dari jumlah partisi n+1
, dan fungsi partisi bertambah
exp(sqrt(n))
. Jadi sementara masih ada cara mudah untuk meningkatkan waktu berjalan dengan menggunakan kembali hasil (menjalankan melalui partisi dalam urutan yang berbeda), untuk peningkatan mendasar kita perlu menghindari melihat setiap partisi secara terpisah.
Menghitungnya dengan cepat
Catat itu (p+q)@r = p@r + q@r
. Dengan sendirinya, ini hanya membantu menghindari beberapa perkalian. Tetapi bersamaan dengan (p+q)#r = p#r + q#r
itu berarti bahwa kita dapat menggabungkan dengan polinomial penambahan sederhana yang sesuai dengan partisi yang berbeda. Kita tidak bisa menambahkan semuanya, karena kita masih perlu tahu dengan mana l
kita harus @
-kombinasi, faktor mana yang harus kita gunakan, dan #
ekstensi- mana yang masih mungkin.
Mari kita gabungkan semua polinomial yang sesuai dengan partisi dengan jumlah dan panjang yang sama, dan sudah memperhitungkan berbagai cara untuk mendistribusikan panjang lintasan digit. Berbeda dari yang saya berspekulasi dalam komentar, saya tidak perlu peduli dengan nilai yang digunakan terkecil atau seberapa sering digunakan, jika saya memastikan bahwa saya tidak akan memperpanjang dengan nilai itu.
Ini kode C ++ saya:
#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" ;
}
Ini menggunakan perpustakaan MP GNU. Pada debian, instal libgmp-dev
. Kompilasi dengan g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx
. Program mengambil argumennya dari stdin. Untuk penentuan waktu, gunakan echo 100 | time ./pl
.
Pada akhirnya a[sum][length][i]
berikan jumlah cara sum
digit yang length
berjalan dapat memberikan angka i
. Selama perhitungan, pada awal m
loop, ini memberikan jumlah cara yang bisa dilakukan dengan angka lebih besar dari m
. Semuanya dimulai dengan
a[0][0][1]=1
. Perhatikan bahwa ini adalah superset dari angka yang kita perlukan untuk menghitung fungsi untuk nilai yang lebih kecil. Jadi pada saat yang hampir bersamaan, kita dapat menghitung semua nilai hingga n
.
Tidak ada rekursi, jadi kami memiliki jumlah tetap dari loop bersarang. (Level sarang terdalam adalah 6.) Setiap loop melewati sejumlah nilai yang linear dalam n
kasus terburuk. Jadi kita hanya perlu waktu polinomial. Jika kita melihat lebih dekat pada nested i
dan j
loop extend
, kita menemukan batas atas untuk j
form N/i
. Itu seharusnya hanya memberikan faktor logaritmik untuk j
loop. Loop terdalam di f
(dengan sumn
dll) serupa. Juga perlu diingat bahwa kami menghitung dengan angka yang tumbuh cepat.
Perhatikan juga bahwa kami menyimpan O(n^3)
angka-angka ini.
Secara eksperimental, saya mendapatkan hasil ini pada perangkat keras yang wajar (i5-4590S):
f(50)
membutuhkan satu detik dan 23 MB, f(100)
membutuhkan 21 detik dan 166 MB, f(200)
perlu 10 menit dan 1,5 GB, dan f(300)
perlu satu jam dan 5,6 GB. Ini menunjukkan kompleksitas waktu yang lebih baik daripada O(n^5)
.
N
.