GAP , 416 Bytes
Gewinnt nicht mit der Codegröße und bei weitem nicht mit der konstanten Zeit, sondern verwendet Mathematik, um viel zu beschleunigen!
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;
Um das unnötige Leerzeichen zu entfernen und eine Zeile mit 416 Bytes zu erhalten, gehen Sie folgendermaßen vor:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Mein alter "für Windows XP entworfener" Laptop kann f(10)
in weniger als einer Minute rechnen und in weniger als einer Stunde noch viel weiter gehen:
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
Wie es funktioniert
Angenommen, wir möchten zunächst nur die Anzahl der perfekten Nummernschilder kennen, die zum Muster passen LDDLLDL
, wobei L
ein Buchstabe und
D
eine Ziffer bezeichnet werden. Angenommen, wir haben eine Liste l
von Zahlen, l[i]
die die Anzahl der Möglichkeiten angibt, wie
die Buchstaben den Wert angeben können i
, und eine ähnliche Liste d
für die Werte, die wir aus den Ziffern erhalten. Dann ist die Anzahl der perfekten Nummernschilder mit gemeinsamem Wert i
gerade
l[i]*d[i]
, und wir erhalten die Anzahl aller perfekten Nummernschilder mit unserem Muster, indem wir dies über alles summiereni
. Lassen Sie uns die Operation bezeichnen, mit der diese Summe berechnet wird l@d
.
Selbst wenn der beste Weg, um diese Listen zu erhalten, darin bestand, alle Kombinationen und Zählwerte auszuprobieren, können wir dies unabhängig für die Buchstaben und Ziffern tun und dabei 26^4+10^3
Fälle anstelle von 26^4*10^3
Fällen betrachten, in denen wir einfach alle Platten durchlaufen, die zum Muster passen. Aber wir können viel besser: l
Ist nur die Liste der Koeffizienten,
(x+x^2+...+x^26)^k
wo k
ist die Anzahl der Buchstaben, hier 4
.
In ähnlicher Weise erhalten wir die Anzahl der Möglichkeiten, um eine Summe von Ziffern in einer Folge von k
Ziffern als die Koeffizienten von zu erhalten (1+x+...+x^9)^k
. Wenn es mehr als ein Lauf von Ziffern ist, müssen wir die entsprechenden Listen mit einer Operation kombinieren , d1#d2
dass in der Position i
als Wert die Summe aller , d1[i1]*d2[i2]
wo i1*i2=i
. Dies ist die Dirichlet-Faltung, die nur das Produkt ist, wenn wir die Listen als Koeffizienten von Dirchlet-Reihen interpretieren. Aber wir haben sie bereits als Polynome (endliche Potenzreihen) verwendet, und es gibt keine gute Möglichkeit, die Operation für sie zu interpretieren. Ich denke, dieses Missverhältnis ist Teil dessen, was es schwierig macht, eine einfache Formel zu finden. Verwenden wir es trotzdem für Polynome und verwenden die gleiche Notation #
. Es ist einfach zu berechnen, wenn ein Operand ein Monom ist: wir habenp(x) # x^k = p(x^k)
. Zusammen mit der Tatsache, dass es bilinear ist, ergibt dies eine schöne (aber nicht sehr effiziente) Möglichkeit, es zu berechnen.
Beachten Sie, dass k
Buchstaben einen Wert von höchstens 26k
und k
einzelne Ziffern einen Wert von ergeben können 9^k
. So werden wir im d
Polynom oft nicht benötigte hohe Potenzen bekommen . Um sie loszuwerden, können wir modulo berechnen x^(maxlettervalue+1)
. Dies beschleunigt erheblich und hilft, obwohl ich es nicht sofort bemerkt habe, sogar beim Golfen, da wir jetzt wissen, dass der Grad d
nicht größer ist als der von l
, was die Obergrenze im Finale vereinfacht Sum
. Wir erreichen eine noch bessere Beschleunigung, wenn wir mod
im ersten Argument von Value
(siehe Kommentare) eine Berechnung durchführen , und wenn wir die gesamte #
Berechnung auf einer niedrigeren Ebene durchführen , erhalten wir eine unglaubliche Beschleunigung. Aber wir versuchen immer noch, eine legitime Antwort auf ein Golfproblem zu sein.
Damit haben wir unser l
und d
und können daraus die Anzahl der perfekten Kennzeichen mit Muster berechnen LDDLLDL
. Das ist die gleiche Zahl wie für das Muster LDLLDDL
. Generell können wir die Reihenfolge der Ziffernreihen unterschiedlicher Länge nach Belieben ändern,
NrArrangements
was die Anzahl der Möglichkeiten ergibt. Und während zwischen den Ziffernfolgen ein Buchstabe stehen muss, sind die anderen Buchstaben nicht festgelegt. Das Binomial
zählt diese Möglichkeiten.
Jetzt müssen noch alle möglichen Arten von Lauflängen-Ziffern durchlaufen werden. r
Läuft durch alle Anzahlen von Läufen, c
durch alle Gesamtzahlen von Ziffern und p
durch alle Partitionen von c
mit
r
Summanden.
Die Gesamtzahl der Partitionen, die wir betrachten, ist zwei weniger als die Anzahl der Partitionen n+1
, und die Partitionsfunktionen wachsen wie
folgt exp(sqrt(n))
. Während es also immer noch einfache Möglichkeiten gibt, die Laufzeit zu verbessern, indem die Ergebnisse wiederverwendet werden (indem die Partitionen in einer anderen Reihenfolge durchlaufen werden), müssen wir für eine grundlegende Verbesserung vermeiden, jede Partition einzeln zu betrachten.
Schnell rechnen
Beachten Sie das (p+q)@r = p@r + q@r
. Dies allein hilft nur, einige Multiplikationen zu vermeiden. Zusammen (p+q)#r = p#r + q#r
bedeutet dies jedoch, dass wir durch einfache Addition Polynome kombinieren können, die verschiedenen Partitionen entsprechen. Wir können nicht einfach addieren sie alle, weil wir nach wie vor , mit denen müssen wissen , l
wir haben @
Mähdreschernahrung, welcher Faktor wir verwenden müssen, und welche #
-extensions sind noch möglich.
Lassen Sie uns alle Polynome, die Partitionen entsprechen, mit derselben Summe und Länge kombinieren und bereits die verschiedenen Arten der Verteilung der Längen von Ziffernfolgen berücksichtigen. Anders als ich in den Kommentaren spekuliert habe, muss ich mich nicht um den kleinsten verwendeten Wert oder wie oft er verwendet wird kümmern, wenn ich sicher gehe, dass ich nicht mit diesem Wert verlängere.
Hier ist mein C ++ Code:
#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" ;
}
Dies verwendet die GNU MP-Bibliothek. Unter Debian installieren libgmp-dev
. Kompilieren mit g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx
. Das Programm bezieht sein Argument aus stdin. Verwenden Sie für das Timing echo 100 | time ./pl
.
Am Ende a[sum][length][i]
wird die Anzahl der Möglichkeiten angegeben, auf die sum
Ziffern in length
Läufen die Anzahl angeben können i
. Während der Berechnung am Anfang der m
Schleife wird die Anzahl der Möglichkeiten angegeben, die mit Zahlen größer als ausgeführt werden können m
. Alles beginnt mit
a[0][0][1]=1
. Beachten Sie, dass dies eine Obermenge der Zahlen ist, die wir benötigen, um die Funktion für kleinere Werte zu berechnen. So konnten wir fast gleichzeitig alle Werte bis zu berechnen n
.
Es gibt keine Rekursion, daher haben wir eine feste Anzahl von verschachtelten Schleifen. (Die tiefste Verschachtelungsebene ist 6.) Jede Schleife durchläuft eine Reihe von Werten, die n
im ungünstigsten Fall linear sind . Wir brauchen also nur Polynomzeit. Wenn wir uns das verschachtelte Element genauer ansehen i
und es j
einschleifen extend
, finden wir eine Obergrenze für j
das Formular N/i
. Das sollte nur einen logarithmischen Faktor für die j
Schleife ergeben. Die innerste Schleife f
(mit sumn
etc) ist ähnlich. Denken Sie auch daran, dass wir mit schnell wachsenden Zahlen rechnen.
Beachten Sie auch, dass wir O(n^3)
diese Nummern speichern .
Experimentell erhalte ich diese Ergebnisse auf vernünftiger Hardware (i5-4590S): Benötigt
f(50)
eine Sekunde und 23 MB, f(100)
benötigt 21 Sekunden und 166 MB, f(200)
benötigt 10 Minuten und 1,5 GB und f(300)
benötigt eine Stunde und 5,6 GB. Dies deutet auf eine Zeitkomplexität hin, die besser ist als O(n^5)
.
N
.