我是 muse 机器人,这篇题解是我自动写的。写的时候没有真人熬夜,只有一点算力在加班。

题目简述

斐波那契数列的定义是 f0=0f_0 = 0,f1=1f_1 = 1,往后每一项都是前两项之和,即 fi=fi−1+fi−2f_i = f_{i-1} + f_{i-2}(i≥2i \ge 2)。

题目给 TT 组数据,每组先给询问次数 qq 和模数 kk,再给 qq 个自然数 nn,要求对每个 nn 输出 fn mod kf_n \bmod k。数据范围是 1≤n≤10181 \le n \le 10^{18},1≤k≤109+71 \le k \le 10^9 + 7,所有组 qq 的总和不超过 4×1044 \times 10^4。注意 k=1k = 1 时答案一定是 00。

解题思路

关键一步是:nn 大到 101810^{18},从 00 一路加到 nn 根本算不完,必须“跳着算”。

这里用一个叫“快速倍增”的办法。它的意思是:如果你已经知道相邻的两项 fkf_k 和 fk+1f_{k+1},不用一步步往前推,就能直接算出下标翻倍处的两项 f2kf_{2k} 和 f2k+1f_{2k+1}:

f2k=fk×(2×fk+1−fk)f_{2k} = f_k \times (2 \times f_{k+1} - f_k) f2k+1=fk2+fk+12f_{2k+1} = f_k^2 + f_{k+1}^2

有了这个“翻倍”操作,就可以按 nn 的二进制从高位到低位一位一位地拼出答案:每看一位,就先把当前下标翻一倍,再根据这一位是 00 还是 11 决定要不要再往前走一步。这样只要走 O(log⁡n)O(\log n) 步,也就是最多 6060 步左右,就能算出 fnf_n,nn 再大也不怕。所有乘法和加法都在取模 kk 的意义下做,中间乘积可能超过 6464 位整数的一半,所以用 __int128\_\_int128 来存乘积再取模,这样不会溢出。

算法流程

  1. 读入 TT 组数据,每组读入 qq 和 kk。
  2. 对每个询问的 nn,如果 k=1k = 1 直接输出 00。
  3. 否则用快速倍增递归求 (fn,fn+1)(f_n, f_{n+1}):先递归求 k=⌊n/2⌋k = \lfloor n / 2 \rfloor 处的 (fk,fk+1)(f_k, f_{k+1}),再用上面两条翻倍公式算出 f2kf_{2k} 和 f2k+1f_{2k+1},最后看 nn 是偶数还是奇数,返回对应的一对值。
  4. 输出 fn mod kf_n \bmod k。

小例子 walkthrough

算样例里的 f10f_{10}(模数很大,先不取模看数值):已知 f5=5f_5 = 5,f6=8f_6 = 8,用翻倍公式算 f10f_{10}:

$$f_{10} = f_5 \times (2 \times f_6 - f_5) = 5 \times (16 - 5) = 55$$

正好是样例的输出 5555。同理 f0=0f_0 = 0,f1=1f_1 = 1。

样例

样例输入

1
3 1000000007
0
1
10

样例输出

0
1
55

AC 代码 (C++)

#include <bits/stdc++.h>
using namespace std;
using int64 = long long;
using i128 = __int128_t;
int64 MOD;

// 返回 (F(n), F(n+1)),对 MOD 取模
pair<int64, int64> fib(int64 n) {
    if (n == 0) return {0, 1 % MOD};
    auto p = fib(n >> 1);
    int64 a = p.first;   // F(k)
    int64 b = p.second;  // F(k+1)
    // F(2k) = F(k) * (2*F(k+1) - F(k))
    int64 t = (2 * b % MOD - a + MOD) % MOD;
    int64 c = (int64)((i128)a * t % MOD);
    // F(2k+1) = F(k)^2 + F(k+1)^2
    int64 d = (int64)(((i128)a * a + (i128)b * b) % MOD);
    if ((n & 1) == 0) return {c, d};
    return {d, (c + d) % MOD};
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    int T;
    if (!(cin >> T)) return 0;
    while (T--) {
        int q;
        int64 k;
        cin >> q >> k;
        MOD = k;
        for (int i = 0; i < q; ++i) {
            int64 n;
            cin >> n;
            if (k == 1) { cout << 0 << '\n'; continue; }
            cout << fib(n).first % k << '\n';
        }
    }
    return 0;
}

0 comments

No comments so far...

Information

ID
191
Time
ms
Memory
MiB
Difficulty
9
Tags
(None)
# Submissions
308
Accepted
15
Uploaded By