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バイトの1行を取得するには、これをパイプ処理します。
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
私の古い「Windows XP用に設計された」ラップトップはf(10)
、1分未満で計算でき、さらに1時間以内にさらに計算できます。
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
position i
に値としてすべてのd1[i1]*d2[i2]
whereの合計を持つ演算と組み合わせる必要がありi1*i2=i
ます。これはディリクレ畳み込みであり、リストをDirchletシリーズの係数として解釈した場合の積にすぎません。しかし、すでにそれらを多項式(有限べき級数)として使用しており、それらの操作を解釈する良い方法はありません。この不一致が、単純な式を見つけるのを難しくしている原因の一部だと思います。とにかく多項式でそれを使用して、同じ表記法を使用してみましょう#
。1つのオペランドが単項式の場合、計算は簡単です。p(x) # x^k = p(x^k)
。双線形であるという事実と併せて、これはそれを計算するための素晴らしい(しかし、あまり効率的ではない)方法を提供します。
k
文字がせいぜい値を与えることに注意してください、
一桁が値を与えることができる26k
間。そのため、多項式で不必要な高出力が得られることがよくあります。それらを取り除くために、moduloを計算できます。これは大きな速度を断念して、私はすぐに気付かなかったけれども、私たちは今の度合いがいることを知っているのででも、ゴルフを支援すること、次に大きなではありません最終的に上限を簡素化しています、。
(コメントを参照)の最初の引数で計算を行うことでさらに高速化され、計算全体を低レベルで実行すると信じられないほど高速化されます。しかし、私たちはまだゴルフの問題に対する正当な答えを目指しています。k
9^k
d
x^(maxlettervalue+1)
d
l
Sum
mod
Value
#
だから私たちは私たちを持っているl
とd
し、パターンとの完璧なナンバープレートの番号を計算するためにそれらを使用することができますLDDLLDL
。それはパターンと同じ数字LDLLDDL
です。一般的に、さまざまな長さの数字の実行順序を好きなように変更することができ
NrArrangements
、可能性の数が与えられます。また、数字の連続間に1つの文字が必要ですが、他の文字は固定されていません。Binomial
これらの可能性をカウントします。
これで、実行桁数の可能なすべての方法を実行することができます。r
すべての実行回数、c
すべての合計桁数、および被加数のp
すべてのパーティションを実行c
し
r
ます。
調べるパーティションの総数は、のパーティション数よりも2つ少なくn+1
、パーティション関数はのように成長し
exp(sqrt(n))
ます。そのため、結果を再利用して(異なる順序でパーティションを実行することで)実行時間を改善する簡単な方法がまだありますが、根本的な改善のために、各パーティションを別々に見ないようにする必要があります。
高速計算
ことに注意してください(p+q)@r = p@r + q@r
。単独で、これは単に乗算を回避するのに役立ちます。しかし、(p+q)#r = p#r + q#r
それとともに、異なるパーティションに対応する単純な加算多項式で結合できることを意味します。我々はまだどのと知っておく必要があるので、私たちはただ、すべてのそれらを追加することはできませんl
私たちがしなければならない@
、我々が使用する必要がどの要因、-combine、そしてどの#
-extensionsはまだ可能です。
同じ合計と長さのパーティションに対応するすべての多項式を組み合わせて、桁数の長さを分散する複数の方法をすでに説明しましょう。コメントで推測したものとは異なり、その値で拡張しないことを確認すれば、最小の使用値や使用頻度を気にする必要はありません。
これが私の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
ます。プログラムは標準入力から引数を取ります。タイミングについては、を使用します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
(with with sumn
)の最も内側のループも同様です。また、急速に成長する数値で計算することにも留意してください。
またO(n^3)
、これらの数値を保存していることにも注意してください。
実験的に、妥当なハードウェア(i5-4590S)でこれらの結果を取得し
f(50)
ます。1秒と23 MB、f(100)
21秒と166 MB、f(200)
10分と1.5 GB、f(300)
1時間と5.6 GBが必要です。これは時間より複雑なことを示唆しO(n^5)
ます。
N
。