GAP , 416 bytes
No ganará en tamaño de código, y lejos del tiempo constante, ¡pero usa las matemáticas para acelerar mucho!
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;
Para exprimir el espacio en blanco innecesario y obtener una línea con 416 bytes, canalice esto:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Mi vieja computadora portátil "diseñada para Windows XP" puede calcular f(10)en menos de un minuto e ir mucho más lejos en menos de una hora:
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
Cómo funciona
Supongamos que primero solo queremos saber el número de placas perfectas que se ajustan al patrón LDDLLDL, donde Ldenota una letra y
Ddenota un dígito. Supongamos que tenemos una lista lde números que
l[i]da la cantidad de formas en que las letras pueden dar el valor i, y una lista similar dpara los valores que obtenemos de los dígitos. Entonces, el número de placas perfectas con valor común ies justo
l[i]*d[i], y obtenemos el número de todas las placas perfectas con nuestro patrón sumando esto sobre todo i. Denotemos la operación de obtener esta suma l@d.
Ahora, incluso si la mejor manera de obtener estas listas era probar todas las combinaciones y contar, podemos hacer esto independientemente para las letras y los dígitos, mirando los 26^4+10^3casos en lugar de los 26^4*10^3
casos cuando simplemente pasamos por todas las placas que se ajustan al patrón. Pero podemos hacerlo mucho mejor: aquí les solo la lista de coeficientes de
(x+x^2+...+x^26)^kdónde kestá el número de letras 4.
Del mismo modo, obtenemos el número de formas de obtener una suma de dígitos en una serie de kdígitos como los coeficientes de (1+x+...+x^9)^k. Si hay más de una serie de dígitos, necesitamos combinar las listas correspondientes con una operación d1#d2que en la posición itenga como valor la suma de todos los d1[i1]*d2[i2]lugares i1*i2=i. Esta es la convolución de Dirichlet, que es solo el producto si interpretamos las listas como coeficientes de las series de Dirchlet. Pero ya los hemos usado como polinomios (series de potencia finita), y no hay una buena manera de interpretar la operación para ellos. Creo que este desajuste es parte de lo que hace que sea difícil encontrar una fórmula simple. Usémoslo en polinomios de todos modos y usemos la misma notación #. Es fácil de calcular cuando un operando es un monomio: tenemosp(x) # x^k = p(x^k). Junto con el hecho de que es bilineal, esto proporciona una forma agradable (pero no muy eficiente) de calcularlo.
Tenga en cuenta que las kletras dan un valor de como máximo 26k, mientras quek
dígitos individuales pueden dar un valor de 9^k. Por lo tanto, a menudo obtendremos altos poderes innecesarios en el dpolinomio. Para deshacernos de ellos, podemos calcular el módulo x^(maxlettervalue+1). Esto da una gran velocidad y, aunque no me di cuenta de inmediato, incluso ayuda al golf, porque ahora sabemos que el grado ded no es mayor que el de l, lo que simplifica el límite superior en la final Sum. Obtenemos una velocidad aún mejor al hacer un modcálculo en el primer argumento de Value
(ver comentarios), y hacer todo el #cálculo a un nivel inferior da una velocidad increíble. Pero todavía estamos tratando de ser una respuesta legítima a un problema de golf.
Así que tenemos nuestro lyd podemos usarlos para calcular el número de matrículas perfectas con patrón LDDLLDL. Ese es el mismo número que para el patrón LDLLDDL. En general, podemos cambiar el orden de las corridas de dígitos de diferente longitud como queramos,
NrArrangementsda el número de posibilidades. Y aunque debe haber una letra entre las series de dígitos, las otras letras no son fijas. El Binomialcuenta estas posibilidades.
Ahora queda por recorrer todas las formas posibles de tener longitudes de dígitos de ejecución. rrecorre todos los números de carreras,c todos los números totales de dígitos y ptodas las particiones de ccon
rsumandos.
El número total de particiones que vemos es dos menos que el número de particiones n+1, y las funciones de partición crecen como
exp(sqrt(n)). Por lo tanto, aunque todavía hay formas fáciles de mejorar el tiempo de ejecución reutilizando los resultados (ejecutando las particiones en un orden diferente), para una mejora fundamental, debemos evitar mirar cada partición por separado.
Calcularlo rápido
Tenga en cuenta que (p+q)@r = p@r + q@r. Por sí solo, esto solo ayuda a evitar algunas multiplicaciones. Pero junto con (p+q)#r = p#r + q#resto significa que podemos combinar mediante polinomios de suma simple correspondientes a diferentes particiones. No podemos simplemente agregarlos a todos, porque aún necesitamos saber con quél tenemos que @combinar, qué factor tenemos que usar y qué #extensiones aún son posibles.
Combinemos todos los polinomios correspondientes a particiones con la misma suma y longitud, y ya tenga en cuenta las múltiples formas de distribuir las longitudes de las series de dígitos. A diferencia de lo que especulé en los comentarios, no necesito preocuparme por el valor usado más pequeño o con qué frecuencia se usa, si me aseguro de no extenderme con ese valor.
Aquí está mi código 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" ;
}
Esto usa la biblioteca GNU MP. En debian, instale libgmp-dev. Compilar con g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx. El programa toma su argumento de stdin. Para el tiempo, useecho 100 | time ./pl .
Al final a[sum][length][i]da el número de formas en que los sum
dígitos en las lengthcorridas pueden dar el número i. Durante el cálculo, al comienzo del mciclo, proporciona la cantidad de formas que se pueden hacer con números mayores que m. Todo comienza con
a[0][0][1]=1. Tenga en cuenta que este es un superconjunto de los números que necesitamos para calcular la función para valores más pequeños. Entonces, casi al mismo tiempo, podríamos calcular todos los valores hasta n.
No hay recursión, por lo que tenemos un número fijo de bucles anidados. (El nivel de anidamiento más profundo es 6.) Cada ciclo pasa por una serie de valores que son lineales nen el peor de los casos. Entonces solo necesitamos tiempo polinomial. Si miramos más de cerca los anidados iy los jbucles extend, encontramos un límite superior para jel formulario N/i. Eso solo debería dar un factor logarítmico para el jbucle. El bucle más interno en f
(consumn etc.) es similar. También tenga en cuenta que calculamos con números que crecen rápidamente.
Tenga en cuenta también que almacenamos O(n^3) estos números.
Experimentalmente, obtengo estos resultados en hardware razonable (i5-4590S):
f(50)necesita un segundo y 23 MB, f(100)necesita 21 segundos y 166 MB, f(200)necesita 10 minutos y 1.5 GB, y f(300)necesita una hora y 5.6 GB. Esto sugiere una complejidad temporal mejor que O(n^5).
N.