GAP , 416 bayt
Kod boyutunda kazanamayacaksınız ve sabit zamandan uzak duracaksınız, ama çok hızlandırmak için matematiği kullanıyor!
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;
Gereksiz boşlukları sıkıştırmak ve 416 byte'lık bir satır almak için aşağıdakileri işaretleyin:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Eski "Windows XP için tasarlanmış" dizüstü bilgisayarım f(10)bir dakikadan daha az bir sürede hesaplayabilir ve bir saat içinde daha da ileri gidebilir:
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
Nasıl çalışır
Biz ilk sadece desen uydurma mükemmel plakalı sayısını bilmek istediğinizi varsayalım LDDLLDL, Lbir mektup gösterir ve
Dbir rakamı ifade eder. Biz bir listesi var varsayalım lböyle sayıların
l[i]harfler değer vermek yolları sayısını verir ive benzer bir liste dbiz basamağı aldığım değerler için. Ardından ortak değere sahip mükemmel plakalı sayısı iadildir
l[i]*d[i]ve hepimiz üzerinden bu toplayarak bizim desenli tüm mükemmel plakalı sayısını almak i. Bu toplamı elde etme işlemini gösterelim l@d.
Şimdi bu listeleri elde etmenin en iyi yolu tüm kombinasyonları denemek ve saymak olsa bile
, bunu sadece desene uyan tüm plakaları incelediğimizde 26^4+10^3durumlara bakmak yerine harflere ve rakamlara bağımsız olarak yapabiliriz 26^4*10^3. Ama biz çok daha iyisini yapabiliriz: lkatsayıları sadece listesidir
(x+x^2+...+x^26)^knerede kharflerin sayısıdır, burada 4.
Benzer şekilde, bir basamak basamağında basamakların toplamının kkatsayıları olarak alınmasının yollarının sayısını alırız (1+x+...+x^9)^k. Birden fazla basamak koşusu varsa, karşılık gelen listeleri d1#d2, konumdaki iher d1[i1]*d2[i2]yerin toplamının değeri kadar olan bir işlemle birleştirmemiz gerekir i1*i2=i. Bu, listeleri Dirchlet serisinin katsayıları olarak yorumladığımız takdirde yalnızca ürün olan Dirichlet evrimidir. Fakat biz bunları zaten polinomlar (sonlu güç serileri) olarak kullandık ve bu operasyonu onlar için yorumlamanın iyi bir yolu yok. Bu uyumsuzluğun basit bir formül bulmayı zorlaştıran şeyin bir parçası olduğunu düşünüyorum. Hadi yine de polinomlarda kullanalım ve aynı notasyonu kullanalım.# . Bir işlenenin monomial olduğu zaman hesaplamak kolaydır:p(x) # x^k = p(x^k). Bilinear olduğu gerçeğiyle birlikte, bu onu hesaplamak için güzel (ama çok verimli değil) bir yol sunar.
Not kharfler en az bir değer vermek 26kederken, k
tek basamaklı bir değer verebilir 9^k. Bu yüzden dpolinomda sıklıkla gereksiz yüksek güçlere sahip olacağız . Onlardan kurtulmak için moduloyu hesaplayabiliriz x^(maxlettervalue+1). Bu büyük bir hız kazandırıyor ve hemen farketmeme rağmen golfe bile yardımcı oluyor, çünkü artık derecenin dbundan daha büyük olmadığını biliyoruz.l finalin üst sınırını basitleştirenSum . modİlk argümanında bir hesaplama yaparak daha da hızlanırız Value
(yorumlara bakın) ve tüm #hesaplamanın daha düşük bir seviyede yapılması inanılmaz bir hızlanma sağlar. Ama hala bir golf problemine meşru bir cevap olmaya çalışıyoruz.
Bu yüzden biz elimizde lve donları mükemmel plaka sayısını desenle hesaplamak için kullanabilirizLDDLLDL . Bu, kalıpla aynı sayıdır LDLLDDL. Genel olarak, farklı uzunluktaki rakam dizilerinin sırasını istediğimiz gibi değiştirebiliriz, NrArrangementsolasılık sayısını veririz
. Ve rakamların arasında bir harf olması gerekirken, diğer harfler sabit değildir. BinomialBu olasılıkları sayar.
Artık, uzunlukları kaç basamak hane sahip olabileceği tüm yollardan geçmeye devam ediyor. rTüm koşu sayıları boyunca, ctüm toplam sayı sayıları ile yapılan vep toplamlarla cbirlikte
tüm bölümleri boyunca rçalıştırır.
Baktığımız toplam bölüm sayısı, bölüm sayısından iki kat daha az n+1 ve bölümleme işlevleri de büyüyor
exp(sqrt(n)). Bu nedenle, sonuçları tekrar kullanarak (bölümleri farklı bir sırada geçirerek) çalışma süresini iyileştirmenin kolay yolları olsa da, temel bir iyileştirme için her bölüme ayrı ayrı bakmaktan kaçınmamız gerekir.
Hızlı hesaplamak
Bunu not al (p+q)@r = p@r + q@r. Kendi başına, bu sadece bazı çarpmalardan kaçınmaya yardımcı olur. Ancak (p+q)#r = p#r + q#rbununla birlikte, farklı bölümlere karşılık gelen basit ek polinomları ile birleşebileceğimiz anlamına gelir. Hepsini ekleyemiyoruz, çünkü hangisini lyapmamız gerektiğini bilmemiz gerekiyor .@ biz kullanıma zorunda ve hangi faktör, -combine #-extensions hala mümkündür.
Bölümlere tekabül eden tüm polinomları aynı toplam ve uzunluk ile birleştirelim ve halihazırda basamak koşularının uzunluğunu dağıtmanın birçok yolunu hesaba katalım. Yorumlarda söylediklerimden farklı olarak, en küçük kullanılan değeri veya ne sıklıkta kullanıldığını umursamama gerek yok, eğer bu değerle uzatacağımdan emin olmazsam.
İşte benim C ++ kodum:
#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" ;
}
Bu GNU MP kütüphanesini kullanır. Debian'da yükleyin libgmp-dev. İle derleyin g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx. Program argümanını stdin'den alıyor. Zamanlama için kullanın echo 100 | time ./pl.
Sonunda, koşulardaki
basamakların sayıyı verebileceği a[sum][length][i]yolların sayısını verir . Hesaplama sırasında, döngünün başlangıcında, büyük sayılarla yapılabilecek yol sayısını verir . Her şey ile başlar
. Bunun, daha küçük değerler için fonksiyonu hesaplamamız için gereken sayıların üst kümesi olduğunu unutmayın. Neredeyse aynı anda, tüm değerleri hesaplayabiliriz .sumlengthimma[0][0][1]=1n
Özyineleme yok, bu nedenle sabit sayıda iç içe geçmiş döngümüz var. (En derin yerleştirme seviyesi 6'dır.) Her döngü nen kötü durumda doğrusal olan bir dizi değerden geçer . Bu yüzden sadece polinom zamana ihtiyacımız var. Eğer iç içe geçmişe bakar ive içine jgirersek , form extendiçin bir üst sınır buluruz . Bu sadece döngü için logaritmik bir faktör vermelidir . İçindeki en içteki döngü
( etc ile) benzerdir. Ayrıca hızlı büyüyen sayılarla hesapladığımızı da unutmayın.jN/ijfsumn
Ayrıca O(n^3)bu numaraları sakladığımızı da unutmayın .
Deneysel olarak, bu sonuçları makul donanım (i5-4590S) ile elde ediyorum:
f(50)bir saniye ve 23 MB f(100)gerekiyor, 21 saniye ve 166 MB f(200)gerekiyor, 10 dakika ve 1,5 GB f(300)gerekiyor ve bir saat 5,6 GB gerekiyor. Bu, zaman karmaşıklığından daha iyi olduğunu gösteriyor O(n^5).
N.