Оскільки може досягати 10^{18}, перебирати числа Фібоначчі по одному неможливо. Матричний метод також не підходить: для суми -х степенів знадобилася б матриця розміру порядку , а може дорівнювати 10^5. Тому скористаємося явною формулою для чисел Фібоначчі — формулою Біне:
де
Числа і є коренями рівняння x^2 = x + 1, і за теоремою Вієта \varphi\psi = -1.
Розкриття степеня. Піднесемо формулу Біне до степеня і розкриємо дужки за біномом Ньютона:
Позначимо
Підсумуємо по від до і змінимо порядок підсумовування:
Таким чином, сума з доданків звелася до суми з k + 1 доданків.
Геометрична прогресія. Внутрішня сума є сумою геометричної прогресії зі знаменником t = t_j:
Випадок t = 1 дійсно виникає і потребує окремої обробки (інакше відбудеться ділення на нуль). Наприклад, якщо ділиться на , то при j = k / 2 отримуємо
Степінь t^n обчислюється бінарним піднесенням до степеня за O(\log n), тому кожен доданок знаходиться швидко навіть при n = 10^{18}.
Послідовне обчислення знаменників. Не потрібно щоразу заново підносити і до степеня для кожного . Сусідні знаменники пов'язані співвідношенням
тому достатньо один раз обчислити t_0 = \varphi^k, а потім на кожній ітерації множити поточне значення на .
Обчислення за модулем. Дійсна арифметика тут не підходить: потрібна точна відповідь за модулем p = 10^9 + 7. Працюватимемо з числами вигляду a + b\sqrt{5}, де і — лишки за модулем . Додавання і множення таких чисел виконуються за правилами
Числа вигляду a + b\sqrt{5} утворюють поле \mathbb{Z}_p[\sqrt{5}]. У цьому полі будь-який ненульовий елемент оборотний:
причому знаменник a^2 - 5b^2 не дорівнює нулю за модулем . Це гарантує коректність усіх ділень у розв'язку. Ділення на у формулах для і замінюється множенням на 2^{-1} \bmod p = (p + 1) / 2.
Обернений елемент до 2. Обернений до елемент за модулем — це таке число , що 2x \equiv 1 \pmod{p}.
Перевіримо, що x = (p + 1) / 2 підходить:
p = 10^9 + 7 — непарне просте число, тому p + 1 парне і (p + 1) / 2 — ціле число. Воно менше за , тобто це звичайний лишок за модулем .
Помножимо його на : 2 \cdot (p + 1) / 2 = p + 1 \equiv 1 \pmod{p}.
Отже, (p + 1) / 2 і є 2^{-1} \bmod p.
Шукана сума є цілим числом, тому в підсумковому значенні a + b\sqrt{5} коефіцієнт обов'язково дорівнюватиме нулю, а відповіддю буде .
Приклад
Розглянемо перший приклад: , \boldsymbol{k = 1}. При k = 1 формула набуває вигляду
Обчислимо степені , користуючись співвідношенням \varphi^2 = \varphi + 1:
Їхня сума дорівнює \dfrac{15 + 7\sqrt{5}}{2}. Степені отримуються заміною \sqrt{5} на -\sqrt{5}, тому їхня сума дорівнює \dfrac{15 - 7\sqrt{5}}{2}. Отже,
що збігається з відповіддю: 1 + 1 + 2 + 3 = 7.
Розглянемо приклад для \boldsymbol{k = 3}. Розкриємо куб різниці:
Знаменники прогресій дорівнюють (використовуємо \varphi\psi = -1):
Тоді
Кожна з чотирьох сум обчислюється за формулою геометричної прогресії. Перевіримо це при n = 3. Степені дорівнюють
Тоді
Суми для отримуються заміною \sqrt{5} на -\sqrt{5}:
Підставляємо знайдені значення:
Результат збігається з безпосереднім обчисленням:
Розглянемо приклад для \boldsymbol{k = 4} (особливий випадок). Тут
і
Середній доданок відповідає знаменнику t_2 = 1: формула геометричної прогресії до нього незастосовна, а сума 1 + 1 + \ldots + 1 дорівнює . Саме цей випадок окремо обробляється в програмі. Наприклад, при n = 3 отримуємо:
Реалізація алгоритму
Оголосимо константу модуля .
#define MOD 1000000007У масиві зберігатимемо значення біноміальних коефіцієнтів.
vector<long long> Cnk;Для реалізації арифметики в полі Z_{MOD} [\sqrt{5}] оголосимо структуру . Структура зберігає число вигляду 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) {}
};Функція add реалізує додавання в полі Z_{MOD} [\sqrt{5}].
Num add(const Num& a, const Num& b)
{
return Num((a.x + b.x) % MOD, (a.y + b.y) % MOD);
}Функція mult реалізує множення в полі 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
);
}Функція pw реалізує бінарне піднесення до степеня в полі Z_{MOD} [\sqrt{5}], де показник степеня є цілим числом.
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;
}Функція pw реалізує швидке бінарне піднесення до степеня a^b за модулем .
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;
}Функція inv обчислює обернений елемент до в полі Z_{MOD} [\sqrt{5}].
Якщо
то оберненим до нього елементом буде
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
);
}Функція divide реалізує ділення в полі Z_{MOD} [\sqrt{5}]:
Num divide(const Num& a, const Num& b)
{
return mult(a, inv(b));
}Основна частина програми. Зчитуємо вхідні дані.
scanf("%lld %lld", &n, &k);Обчислюємо біноміальні коефіцієнти C_k^0, C_k^1, C_k^2, ..., C_k^k і заносимо їх у масив . Зазначимо, що
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;
}Обчислюємо inv2 = 2^{-1} \mod MOD = (MOD + 1) / 2.
long long inv2 = (MOD + 1) / 2;Ініціалізуємо константи: phi = \phi = \frac{1 + \sqrt{5}}{2} і psi = \psi = \frac{1 - \sqrt{5}}{2}.
Num phi(inv2, inv2);
Num psi(inv2, MOD - inv2);Обчислюємо ratio = \psi / \phi.
Num ratio = divide(psi, phi);Ініціалізуємо константи і в полі Z_{MOD} [\sqrt{5}].
Num one(1, 0);
Num minus_one(MOD - 1, 0);Відповідь накопичуватимемо у змінній .
Num res(0, 0);Ініціалізуємо t = \phi^k. Далі в циклі множитимемо це значення на , отримуючи
Num t = pw(phi, k);У змінній обчислюємо суму
Шукана сума містить k + 1 доданків. Тому виконуємо k + 1 ітерацій.
for (j = 0; j <= k; j++)
{
Num term;На даний момент t = \phi^{k-j} \psi^j. Обчислюємо суму геометричної прогресії:
Однак випадок t = 1 слід обробити окремо, оскільки у формулі суми геометричної прогресії буде ділення на . При t = 1 сума набуває вигляду 1 + 1 + 1 + ... + 1 ( разів) і дорівнює .
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)
}Обчислюємо 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;Додаємо до результату черговий доданок .
res = add(res, term);Множимо на , отримуємо t = \phi^{k-j-1} \psi^{j + 1}.
t = mult(t, ratio);
}Залишилося поділити на (\sqrt{5})^k.
Num sqrt5(0, 1);
Num sqrt5k = pw(sqrt5, k);
res = divide(res, sqrt5k);Виводимо відповідь.
printf("%lld\n", res.x);