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 Lein Buchstabe und
Deine Ziffer bezeichnet werden. Angenommen, wir haben eine Liste lvon Zahlen, l[i]die die Anzahl der Möglichkeiten angibt, wie
die Buchstaben den Wert angeben können i, und eine ähnliche Liste dfür die Werte, die wir aus den Ziffern erhalten. Dann ist die Anzahl der perfekten Nummernschilder mit gemeinsamem Wert igerade
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^3Fä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: lIst nur die Liste der Koeffizienten,
(x+x^2+...+x^26)^kwo kist 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 kZiffern 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#d2dass in der Position ials 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 kBuchstaben einen Wert von höchstens 26kund k
einzelne Ziffern einen Wert von ergeben können 9^k. So werden wir im dPolynom 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 dnicht größer ist als der von l, was die Obergrenze im Finale vereinfacht Sum. Wir erreichen eine noch bessere Beschleunigung, wenn wir modim 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 lund dund 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,
NrArrangementswas die Anzahl der Möglichkeiten ergibt. Und während zwischen den Ziffernfolgen ein Buchstabe stehen muss, sind die anderen Buchstaben nicht festgelegt. Das Binomialzählt diese Möglichkeiten.
Jetzt müssen noch alle möglichen Arten von Lauflängen-Ziffern durchlaufen werden. rLäuft durch alle Anzahlen von Läufen, cdurch alle Gesamtzahlen von Ziffern und pdurch alle Partitionen von cmit
rSummanden.
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#rbedeutet 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 , lwir 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 lengthLäufen die Anzahl angeben können i. Während der Berechnung am Anfang der mSchleife 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 nim ungünstigsten Fall linear sind . Wir brauchen also nur Polynomzeit. Wenn wir uns das verschachtelte Element genauer ansehen iund es jeinschleifen extend, finden wir eine Obergrenze für jdas Formular N/i. Das sollte nur einen logarithmischen Faktor für die jSchleife ergeben. Die innerste Schleife f
(mit sumnetc) 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.