GAP , 416 byte
Non vincerà sulla dimensione del codice, e lungi dall'essere un tempo costante, ma usa la matematica per accelerare molto!
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;
Per eliminare lo spazio bianco non necessario e ottenere una riga con 416 byte, passare attraverso questo:
sed -e 's/^ *//' -e 's/in \[/in[/' -e 's/ do/do /' | tr -d \\n
Il mio vecchio laptop "progettato per Windows XP" può calcolare f(10)in meno di un minuto e andare molto più avanti in meno di un'ora:
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
Come funziona
Supponiamo di voler prima conoscere solo il numero di targhe perfette che si adattano al modello LDDLLDL, dove Lindica una lettera e
Dindica una cifra. Supponiamo di avere un elenco ldi numeri tale
l[i]da fornire il numero di modi in cui le lettere possono dare il valore ie un elenco simile dper i valori che otteniamo dalle cifre. Quindi il numero di targhe perfette con un valore comune iè giusto
l[i]*d[i]e otteniamo il numero di tutte le targhe perfette con il nostro modello sommando questo su tutto i. Indichiamo l'operazione per ottenere questa somma l@d.
Ora, anche se il modo migliore per ottenere questi elenchi fosse provare tutte le combinazioni e contare, possiamo farlo in modo indipendente per le lettere e le cifre, osservando i 26^4+10^3casi anziché i 26^4*10^3
casi quando passiamo attraverso tutte le piastre che si adattano al modello. Ma possiamo fare molto meglio: lè solo l'elenco dei coefficienti di
(x+x^2+...+x^26)^kdove kè il numero di lettere, qui 4.
Allo stesso modo, otteniamo il numero di modi per ottenere una somma di cifre in una serie di kcifre come coefficienti di (1+x+...+x^9)^k. Se v'è più di una corsa di cifre, è necessario combinare gli elenchi corrispondenti con un'operazione d1#d2che nella posizione iha come valore la somma di tutti d1[i1]*d2[i2]dove i1*i2=i. Questa è la convoluzione di Dirichlet, che è solo il prodotto se interpretiamo le liste come coefficienti delle serie di Dirchlet. Ma li abbiamo già usati come polinomi (serie di potenze finite) e non esiste un buon modo per interpretare l'operazione per loro. Penso che questa mancata corrispondenza faccia parte di ciò che rende difficile trovare una formula semplice. Usiamolo comunque sui polinomi e usiamo la stessa notazione #. È facile calcolare quando un operando è un monomio: abbiamop(x) # x^k = p(x^k). Insieme al fatto che è bilineare, questo fornisce un modo piacevole (ma non molto efficiente) per calcolarlo.
Nota che le klettere danno un valore al massimo 26k, mentre le k
singole cifre possono dare un valore di 9^k. Quindi spesso otterremo alti poteri non necessari nel dpolinomio. Per sbarazzarci di loro, possiamo calcolare il modulo x^(maxlettervalue+1). Questo dà una grande velocità e, anche se non l'ho notato immediatamente, aiuta anche a giocare a golf, perché ora sappiamo che il grado di dnon è maggiore di quello dil , il che semplifica il limite superiore in finale Sum. Otteniamo un'accelerazione ancora migliore facendo un modcalcolo nel primo argomento di Value
(vedi commenti) e facendo l'intero #calcolo a un livello inferiore si ottiene un'incredibile velocità. Ma stiamo ancora cercando di essere una risposta legittima a un problema di golf.
Quindi abbiamo il nostro le dpossiamo usarli per calcolare il numero di targhe perfette con motivoLDDLLDL . Questo è lo stesso numero del modello LDLLDDL. In generale, possiamo cambiare l'ordine delle serie di cifre di diversa lunghezza a nostro piacimento,
NrArrangementsdando il numero di possibilità. E mentre ci deve essere una lettera tra le serie di cifre, le altre lettere non sono fisse. Il Binomialconta queste possibilità.
Ora resta da percorrere tutti i modi possibili per avere lunghezze di cifre di esecuzione. rcorre attraverso tutti i numeri di corse, cattraverso tutti i numeri totali di cifre ep attraverso tutte le partizioni di ccon
rsomme.
Il numero totale di partizioni che osserviamo è due in meno rispetto al numero di partizioni di n+1 , e le funzioni di partizione crescono come
exp(sqrt(n)). Quindi, mentre ci sono ancora modi semplici per migliorare il tempo di esecuzione riutilizzando i risultati (scorrendo le partizioni in un ordine diverso), per un miglioramento fondamentale dobbiamo evitare di guardare ciascuna partizione separatamente.
Calcolo veloce
Si noti che (p+q)@r = p@r + q@r. Da solo, questo aiuta solo ad evitare alcune moltiplicazioni. Ma insieme ad (p+q)#r = p#r + q#resso significa che possiamo combinare con semplici polinomi di aggiunta corrispondenti a diverse partizioni. Non possiamo semplicemente aggiungerli tutti, perché dobbiamo ancora sapere con chi ldobbiamo@ combinare, quale fattore dobbiamo usare e quali #estensioni sono ancora possibili.
Combiniamo tutti i polinomi corrispondenti alle partizioni con la stessa somma e lunghezza e già spieghiamo i molteplici modi di distribuire le lunghezze delle serie di cifre. Diversamente da quanto ho ipotizzato nei commenti, non ho bisogno di preoccuparmi del valore usato più piccolo o della frequenza con cui viene utilizzato, se mi assicuro di non estenderlo con quel valore.
Ecco il mio codice 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" ;
}
Questo utilizza la libreria GNU MP. Su debian, installa libgmp-dev. Compila con g++ -std=c++11 -O3 -o pl pl.cpp -lgmp -lgmpxx. Il programma prende le sue argomentazioni da stdin. Per i tempi, utilizzare echo 100 | time ./pl.
Alla fine a[sum][length][i]indica il numero di modi in cui le sum
cifre nelle lengthcorse possono indicare il numero i. Durante il calcolo, all'inizio del mciclo, fornisce il numero di modi che è possibile eseguire con numeri maggiori di m. Tutto inizia con
a[0][0][1]=1. Si noti che questo è un superset dei numeri di cui abbiamo bisogno per calcolare la funzione per valori più piccoli. Quindi quasi allo stesso tempo, potremmo calcolare tutti i valori fino a n.
Non c'è ricorsione, quindi abbiamo un numero fisso di loop nidificati. (Il livello di annidamento più profondo è 6.) Ogni ciclo passa attraverso un numero di valori che è lineare nel ncaso peggiore. Quindi abbiamo bisogno solo del tempo polinomiale. Se osserviamo più da vicino i nidificati ie gli janelli extend, troviamo un limite superiore per jil modulo N/i. Ciò dovrebbe solo dare un fattore logaritmico per il jciclo. Il loop più interno in f
(con sumnetc) è simile. Tieni anche presente che calcoliamo con numeri che crescono rapidamente.
Si noti inoltre che memorizziamo O(n^3)questi numeri.
Sperimentalmente, ottengo questi risultati su hardware ragionevole (i5-4590S): ha
f(50)bisogno di un secondo e 23 MB, f(100)richiede 21 secondi e 166 MB, ha f(200)bisogno di 10 minuti e 1,5 GB e f(300)richiede un'ora e 5,6 GB. Ciò suggerisce una complessità temporale migliore di O(n^5).
N.