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
, L
bir mektup gösterir ve
D
bir rakamı ifade eder. Biz bir listesi var varsayalım l
böyle sayıların
l[i]
harfler değer vermek yolları sayısını verir i
ve benzer bir liste d
biz basamağı aldığım değerler için. Ardından ortak değere sahip mükemmel plakalı sayısı i
adildir
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^3
durumlara bakmak yerine harflere ve rakamlara bağımsız olarak yapabiliriz 26^4*10^3
. Ama biz çok daha iyisini yapabiliriz: l
katsayıları sadece listesidir
(x+x^2+...+x^26)^k
nerede k
harflerin sayısıdır, burada 4
.
Benzer şekilde, bir basamak basamağında basamakların toplamının k
katsayı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 i
her 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 k
harfler en az bir değer vermek 26k
ederken, k
tek basamaklı bir değer verebilir 9^k
. Bu yüzden d
polinomda 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 d
bundan 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 l
ve d
onları 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, NrArrangements
olasılık sayısını veririz
. Ve rakamların arasında bir harf olması gerekirken, diğer harfler sabit değildir. Binomial
Bu olasılıkları sayar.
Artık, uzunlukları kaç basamak hane sahip olabileceği tüm yollardan geçmeye devam ediyor. r
Tüm koşu sayıları boyunca, c
tüm toplam sayı sayıları ile yapılan vep
toplamlarla c
birlikte
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#r
bununla birlikte, farklı bölümlere karşılık gelen basit ek polinomları ile birleşebileceğimiz anlamına gelir. Hepsini ekleyemiyoruz, çünkü hangisini l
yapmamı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 .sum
length
i
m
m
a[0][0][1]=1
n
Ö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ü n
en 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 i
ve içine j
girersek , form extend
iç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.j
N/i
j
f
sumn
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
.