Poiché può arrivare fino a 10^{18}, non è possibile scorrere i numeri di Fibonacci uno alla volta. Nemmeno il metodo matriciale è adatto: per la somma delle potenze -esime servirebbe una matrice di dimensione dell'ordine di , e può arrivare fino a 10^5. Utilizziamo quindi una formula esplicita per i numeri di Fibonacci — la formula di Binet:
dove
I numeri e sono le radici dell'equazione x^2 = x + 1 e, per le formule di Viète, \varphi\psi = -1.
Sviluppo della potenza. Eleviamo la formula di Binet alla potenza e sviluppiamo con il binomio di Newton:
Poniamo
Sommiamo su da a e scambiamo l'ordine di sommatoria:
In questo modo la somma di termini si riduce a una somma di k + 1 termini.
Progressione geometrica. La somma interna è la somma di una progressione geometrica di ragione t = t_j:
Il caso t = 1 si presenta davvero e va gestito separatamente (altrimenti si avrebbe una divisione per zero). Ad esempio, se è divisibile per , allora per j = k / 2 si ottiene
La potenza t^n si calcola con l'esponenziazione binaria in tempo O(\log n), quindi ogni termine si trova rapidamente anche per n = 10^{18}.
Calcolo iterativo delle ragioni. Non è necessario elevare di nuovo e a potenza per ogni . Le ragioni consecutive sono legate dalla relazione
quindi basta calcolare una sola volta t_0 = \varphi^k e poi, a ogni iterazione, moltiplicare il valore corrente per .
Calcoli modulari. L'aritmetica in virgola mobile qui non è adatta: serve la risposta esatta modulo p = 10^9 + 7. Lavoreremo con numeri della forma a + b\sqrt{5}, dove e sono residui modulo . L'addizione e la moltiplicazione di tali numeri seguono le regole
I numeri della forma a + b\sqrt{5} formano il campo \mathbb{Z}_p[\sqrt{5}]. In questo campo ogni elemento non nullo è invertibile:
dove il denominatore a^2 - 5b^2 è diverso da zero modulo . Ciò garantisce la correttezza di tutte le divisioni nella soluzione. La divisione per nelle formule di e viene sostituita dalla moltiplicazione per 2^{-1} \bmod p = (p + 1) / 2.
L'inverso di 2. L'inverso di modulo è un numero tale che 2x \equiv 1 \pmod{p}.
Verifichiamo che x = (p + 1) / 2 soddisfa questa condizione:
p = 10^9 + 7 è un numero primo dispari, quindi p + 1 è pari e (p + 1) / 2 è un numero intero. Esso è minore di , cioè è un normale residuo modulo .
Moltiplichiamolo per : 2 \cdot (p + 1) / 2 = p + 1 \equiv 1 \pmod{p}.
Quindi (p + 1) / 2 è proprio 2^{-1} \bmod p.
La somma cercata è un numero intero, quindi nel valore finale a + b\sqrt{5} il coefficiente è necessariamente nullo e la risposta è .
Esempio
Consideriamo il primo esempio: , \boldsymbol{k = 1}. Per k = 1 la formula diventa
Calcoliamo le potenze di usando la relazione \varphi^2 = \varphi + 1:
La loro somma è \dfrac{15 + 7\sqrt{5}}{2}. Le potenze di si ottengono sostituendo \sqrt{5} con -\sqrt{5}, quindi la loro somma è \dfrac{15 - 7\sqrt{5}}{2}. Pertanto,
il che coincide con la risposta: 1 + 1 + 2 + 3 = 7.
Consideriamo un esempio per \boldsymbol{k = 3}. Sviluppiamo il cubo della differenza:
Le ragioni delle progressioni sono (usiamo \varphi\psi = -1):
Allora
Ciascuna delle quattro somme si calcola con la formula della progressione geometrica. Verifichiamolo per n = 3. Le potenze di sono
Allora
Le somme per si ottengono sostituendo \sqrt{5} con -\sqrt{5}:
Sostituendo i valori trovati:
Il risultato coincide con il calcolo diretto:
Consideriamo un esempio per \boldsymbol{k = 4} (caso particolare). Qui
e
Il termine centrale corrisponde alla ragione t_2 = 1: la formula della progressione geometrica non è applicabile e la somma 1 + 1 + \ldots + 1 vale . È proprio questo il caso gestito separatamente nel programma. Ad esempio, per n = 3 si ottiene:
Implementazione dell'algoritmo
Dichiariamo una costante per il modulo .
#define MOD 1000000007Memorizzeremo i valori dei coefficienti binomiali nell'array .
vector<long long> Cnk;Per implementare l'aritmetica nel campo Z_{MOD} [\sqrt{5}] definiamo la struttura . Questa struttura rappresenta i numeri della forma x + y \sqrt{5}.
struct Num
{
long long x, y;
Num(long long x = 0, long long y = 0) : x(x% MOD), y(y% MOD) {}
};La funzione add implementa l'addizione nel campo Z_{MOD} [\sqrt{5}].
Num add(const Num& a, const Num& b)
{
return Num((a.x + b.x) % MOD, (a.y + b.y) % MOD);
}La funzione mult implementa la moltiplicazione nel campo Z_{MOD} [\sqrt{5}].
Num mult(const Num& a, const Num& b)
{
return Num(
(a.x * b.x + 5 * a.y % MOD * b.y) % MOD,
(a.x * b.y + a.y * b.x) % MOD
);
}La funzione pw implementa l'esponenziazione binaria nel campo Z_{MOD} [\sqrt{5}], dove l'esponente è un numero intero.
Num pw(Num base, long long exp)
{
Num res(1, 0);
while (exp)
{
if (exp & 1) res = mult(res, base);
base = mult(base, base);
exp >>= 1;
}
return res;
}La funzione pw implementa l'esponenziazione binaria veloce per calcolare a^b modulo .
long long pw(long long a, long long b)
{
long long res = 1;
a %= MOD;
while (b)
{
if (b & 1) res = (res * a) % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}La funzione inv calcola l'inverso moltiplicativo di un elemento nel campo Z_{MOD} [\sqrt{5}].
Se
allora il suo inverso è dato da
Num inv(const Num& a)
{
long long den = (a.x * a.x - 5 * a.y * a.y) % MOD;
if (den < 0) den += MOD;
long long invDen = pw(den, MOD - 2);
return Num(
a.x * invDen % MOD,
(MOD - a.y) * invDen % MOD
);
}La funzione divide implementa la divisione nel campo Z_{MOD} [\sqrt{5}]:
Num divide(const Num& a, const Num& b)
{
return mult(a, inv(b));
}Parte principale del programma. Leggiamo i dati di input.
scanf("%lld %lld", &n, &k);Calcoliamo i coefficienti binomiali C_k^0, C_k^1, C_k^2, ..., C_k^k e li memorizziamo nell'array . Osserviamo che
Cnk.resize(k + 1);
Cnk[0] = 1;
for (i = 1; i <= k; i++)
{
Cnk[i] = Cnk[i - 1] * (k - i + 1) % MOD;
Cnk[i] = Cnk[i] * pw(i, MOD - 2) % MOD;
}Calcoliamo inv2 = 2^{-1} \mod MOD = (MOD + 1) / 2.
long long inv2 = (MOD + 1) / 2;Inizializziamo le costanti: phi = \phi = \frac{1 + \sqrt{5}}{2} e psi = \psi = \frac{1 - \sqrt{5}}{2}.
Num phi(inv2, inv2);
Num psi(inv2, MOD - inv2);Calcoliamo ratio = \psi / \phi.
Num ratio = divide(psi, phi);Inizializziamo le costanti e nel campo Z_{MOD} [\sqrt{5}].
Num one(1, 0);
Num minus_one(MOD - 1, 0);La risposta verrà accumulata nella variabile .
Num res(0, 0);Inizializziamo t = \phi^k. Successivamente, nel ciclo, moltiplicheremo questo valore per , ottenendo
Num t = pw(phi, k);Nella variabile calcoliamo la somma
La somma cercata contiene k + 1 termini. Pertanto eseguiamo k + 1 iterazioni.
for (j = 0; j <= k; j++)
{
Num term;A questo punto t = \phi^{k-j} \psi^j. Calcoliamo la somma della progressione geometrica:
Tuttavia il caso t = 1 va gestito separatamente, perché la formula della somma di una progressione geometrica contiene una divisione per . Per t = 1 la somma assume la forma 1 + 1 + 1 + ... + 1 ( volte) ed è uguale a .
if (t.x == 1 && t.y == 0)
term = Num(n % MOD, 0);
else
{
Num tn = pw(t, n);
Num num = add(tn, minus_one); // t^n - 1
term = mult(t, num); // t * (t^n - 1)
Num den = add(t, minus_one); // t - 1
term = divide(term, den); // t * (t^n - 1) / (t - 1)
}Calcoliamo term = (-1)^j C_k^j \cdot term.
long long coef = Cnk[j];
if (j & 1) coef = MOD - coef;
term.x = term.x * coef % MOD;
term.y = term.y * coef % MOD;Aggiungiamo il termine successivo al risultato .
res = add(res, term);Moltiplichiamo per , ottenendo t = \phi^{k-j-1} \psi^{j + 1}.
t = mult(t, ratio);
}Resta da dividere per (\sqrt{5})^k.
Num sqrt5(0, 1);
Num sqrt5k = pw(sqrt5, k);
res = divide(res, sqrt5k);Stampiamo la risposta.
printf("%lld\n", res.x);