GAP , 416 octets
Ne gagnera pas sur la taille du code, et loin du temps constant, mais utilise les mathématiques pour accélérer beaucoup!
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;
Pour réduire les espaces inutiles et obtenir une ligne de 416 octets, dirigez-le:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Mon ancien ordinateur portable "conçu pour Windows XP" peut calculer f(10)en moins d'une minute et aller beaucoup plus loin en moins d'une heure:
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
Comment ça marche
Supposons que nous ne voulions d'abord connaître que le nombre de plaques d'immatriculation parfaites correspondant au motif LDDLLDL, où correspond à Lune lettre et à
Dun chiffre. Supposons que nous ayons une liste lde nombres
l[i]donnant le nombre de façons dont les lettres peuvent donner la valeur iet une liste similaire dpour les valeurs que nous obtenons des chiffres. Ensuite, le nombre de plaques d'immatriculation parfaites ayant une valeur commune iest juste
l[i]*d[i], et nous obtenons le nombre de toutes les plaques d'immatriculation parfaites avec notre modèle en additionnant l'ensemble i. Notons l'opération consistant à obtenir cette somme par l@d.
Maintenant, même si le meilleur moyen d’obtenir ces listes était d’essayer toutes les combinaisons et de compter, nous pouvons le faire indépendamment pour les lettres et les chiffres, en regardant les 26^4+10^3cas au lieu des 26^4*10^3
cas où nous passons simplement en revue toutes les plaques correspondant au modèle. Mais nous pouvons faire beaucoup mieux: lest juste la liste des coefficients de
(x+x^2+...+x^26)^koù kest le nombre de lettres, ici 4.
De même, nous obtenons le nombre de façons d'obtenir une somme de chiffres dans une suite de kchiffres en tant que coefficients de (1+x+...+x^9)^k. S'il y a plus d'une suite de chiffres, nous devons combiner les listes correspondantes avec une opération d1#d2dont position ia la valeur la somme de tous d1[i1]*d2[i2]où i1*i2=i. Il s'agit de la convolution de Dirichlet, qui est simplement le produit si nous interprétons les listes comme des coefficients de la série de Dirchlet. Mais nous les avons déjà utilisés en tant que polynômes (séries de puissance finie) et il n’existe aucun moyen d’interpréter l’opération pour eux. Je pense que cette inadéquation fait partie de ce qui rend difficile de trouver une formule simple. Utilisons-le quand même sur les polynômes et utilisons la même notation #. Il est facile de calculer quand un opérande est un monôme: on ap(x) # x^k = p(x^k). Avec le fait qu'il soit bilinéaire, cela donne un moyen agréable (mais pas très efficace) de le calculer.
Notez que les klettres donnent une valeur d'au plus 26k, alors que k
les chiffres simples peuvent donner une valeur de 9^k. Nous aurons donc souvent des pouvoirs élevés inutiles dans le dpolynôme. Pour nous en débarrasser, nous pouvons calculer modulo x^(maxlettervalue+1). Cela donne une grande vitesse et, bien que je ne l'aie pas remarqué tout de suite, aide même au golf, car nous savons maintenant que le degré de dn'est pas plus grand que celui de l, ce qui simplifie la limite supérieure en finale Sum. Nous obtenons une vitesse encore meilleure en faisant un modcalcul dans le premier argument de Value
(voir les commentaires), et en effectuant tout le #calcul à un niveau inférieur, cela donne une vitesse incroyable. Mais nous essayons toujours d'être une réponse légitime à un problème de golf.
Nous avons donc nos let nous dpouvons les utiliser pour calculer le nombre de plaques d'immatriculation parfaites avec un motif LDDLLDL. C'est le même nombre que pour le motif LDLLDDL. En général, nous pouvons modifier l'ordre des séquences de chiffres de longueur différente selon
NrArrangementsle nombre de possibilités. Et s'il doit y avoir une lettre entre les séries de chiffres, les autres lettres ne sont pas fixes. Le Binomialcompte ces possibilités.
Il reste maintenant à parcourir tous les moyens possibles pour avoir des longueurs de chiffres de courses. rpasse à travers tous les numéros de pistes, à ctravers tous le nombre total de chiffres, et à ptravers toutes les partitions de cavec
rsummands.
Le nombre total de partitions que nous examinons est deux de moins que le nombre de partitions n+1, et les fonctions de partition se développent comme suit
exp(sqrt(n)). Il est donc toujours facile d'améliorer le temps d'exécution en réutilisant les résultats (en parcourant les partitions dans un ordre différent), mais pour une amélioration fondamentale, nous devons éviter de regarder chaque partition séparément.
Le calcul rapide
Notez que (p+q)@r = p@r + q@r. En soi, cela permet d'éviter certaines multiplications. Mais (p+q)#r = p#r + q#rcela signifie que nous pouvons combiner par simple addition des polynômes correspondant à différentes partitions. Nous ne pouvons pas simplement les ajouter tous, car nous avons encore besoin de savoir avec lequel lnous devons @combiner, quel facteur nous devons utiliser et quelles #extensions sont encore possibles.
Associons tous les polynômes correspondant aux partitions ayant la même somme et la même longueur, et prenons déjà en compte les multiples façons de répartir les longueurs des séries de chiffres. Contrairement à ce que j'ai spéculé dans les commentaires, je n'ai pas besoin de me soucier de la plus petite valeur utilisée ou de la fréquence d'utilisation, si je m'assure que je ne vais pas prolonger cette valeur.
Voici mon code 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" ;
}
Ceci utilise la bibliothèque GNU MP. Sur Debian, installez libgmp-dev. Compiler avec g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx. Le programme tire son argument de stdin. Pour le timing, utilisez echo 100 | time ./pl.
À la fin, a[sum][length][i]donne le nombre de façons dont les sum
chiffres dans les lengthexécutions peuvent donner le nombre i. Lors du calcul, au début de la mboucle, il indique le nombre de façons de procéder avec des nombres supérieurs à m. Tout commence par
a[0][0][1]=1. Notez que ceci est un sur-ensemble des nombres dont nous avons besoin pour calculer la fonction pour des valeurs plus petites. Donc, presque au même moment, nous pourrions calculer toutes les valeurs jusqu’à n.
Il n'y a pas de récursivité, nous avons donc un nombre fixe de boucles imbriquées. (Le niveau d'imbrication le plus profond est 6.) Chaque boucle passe par un nombre de valeurs qui est linéaire ndans le pire des cas. Nous avons donc besoin que de temps polynomial. Si nous regardons de plus près l'imbrication iet la jboucle extend, nous trouvons une limite supérieure pour jla forme N/i. Cela ne devrait donner qu'un facteur logarithmique pour la jboucle. La boucle la plus interne f
(avec sumnetc.) est similaire. Gardez également à l'esprit que nous calculons avec des nombres qui croissent rapidement.
Notez également que nous stockons O(n^3)ces nombres.
Expérimentalement, j'obtiens ces résultats sur un matériel raisonnable (i5-4590S):
f(50)nécessite une seconde et 23 Mo, f(100)21 secondes et 166 Mo, f(200)10 minutes et 1,5 Go, et f(300)une heure et 5,6 Go. Cela suggère une complexité temporelle meilleure que O(n^5).
N.