```mermaid graph LR A[Mermaid Keeper] --> B[保持激活] ```

前言

02-04 快速幂与逆元 里已经介绍过矩阵快速幂的基本原理:把递推式 an=2an1+an2a_n = 2a_{n-1} + a_{n-2} 写成矩阵形式,然后用快速幂在 O(logN)O(\log N) 内求出第 NN 项。那篇的例题 5 给出了一个 2×22 \times 2 矩阵的完整实现。

这篇是矩阵快速幂的进阶篇。我们会处理更高阶的递推(Tribonacci 三阶递推、kk 阶递推),以及更复杂的转移矩阵构造方法。核心思想不变:任何常系数线性递推都可以写成矩阵乘法

问题的本质

从递推到矩阵

斐波那契递推 fn=fn1+fn2f_n = f_{n-1} + f_{n-2} 可以写成:

(fnfn1)=(1110)(fn1fn2)\begin{pmatrix} f_n \\ f_{n-1} \end{pmatrix} = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix} \begin{pmatrix} f_{n-1} \\ f_{n-2} \end{pmatrix}

展开验证:fn=1fn1+1fn2f_n = 1 \cdot f_{n-1} + 1 \cdot f_{n-2}fn1=1fn1+0fn2f_{n-1} = 1 \cdot f_{n-1} + 0 \cdot f_{n-2}。✓

递推 n1n-1 次就得到:

(fnfn1)=Mn2(f2f1)\begin{pmatrix} f_n \\ f_{n-1} \end{pmatrix} = M^{n-2} \begin{pmatrix} f_2 \\ f_1 \end{pmatrix}

其中 M=(1110)M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}Mn2M^{n-2} 用快速幂 O(logN)O(\log N) 算出。

高阶递推的推广

三阶递推 an=an1+an2+an3a_n = a_{n-1} + a_{n-2} + a_{n-3}(Tribonacci)的转移矩阵是 3×33 \times 3

(anan1an2)=(111100010)(an1an2an3)\begin{pmatrix} a_n \\ a_{n-1} \\ a_{n-2} \end{pmatrix} = \begin{pmatrix} 1 & 1 & 1 \\ 1 & 0 & 0 \\ 0 & 1 & 0 \end{pmatrix} \begin{pmatrix} a_{n-1} \\ a_{n-2} \\ a_{n-3} \end{pmatrix}

规律:转移矩阵的第一行就是递推系数1,1,11, 1, 1),其余行是单位向量偏移(把第 ii 行第 i1i-1 列设为 1,用来”向下移动”状态)。

一般地,kk 阶常系数线性递推 an=c1an1+c2an2++ckanka_n = c_1 a_{n-1} + c_2 a_{n-2} + \cdots + c_k a_{n-k} 对应 k×kk \times k 转移矩阵:

M=(c1c2ck1000100010)M = \begin{pmatrix} c_1 & c_2 & \cdots & c_k \\ 1 & 0 & \cdots & 0 \\ 0 & 1 & \cdots & 0 \\ \vdots & & \ddots & \vdots \\ 0 & 0 & \cdots & 1 & 0 \end{pmatrix}

复杂度

K×KK \times K 矩阵乘法 O(K3)O(K^3),快速幂 O(logN)O(\log N) 次乘法,总时间 O(K3logN)O(K^3 \log N)K=3,N=1018K=3, N=10^{18}27×60160027 \times 60 \approx 1600 次乘法,瞬间跑完。

理论 + 代码

通用矩阵快速幂模板

#include <cstdio>
#include <cstring>
using namespace std;

const long long MOD = 1000000007;

struct Mat {
    static const int MAXK = 10;
    long long a[MAXK][MAXK];
    int n; // 矩阵大小 n × n
    Mat(int n = 2) : n(n) { memset(a, 0, sizeof(a)); }
};

Mat mul(const Mat& A, const Mat& B) {
    Mat C(A.n);
    for (int i = 0; i < A.n; i++)
        for (int k = 0; k < A.n; k++)
            for (int j = 0; j < A.n; j++)
                C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD; // ① 三重循环
    return C;
}

Mat power(Mat A, long long e) {
    Mat R(A.n);
    for (int i = 0; i < A.n; i++) R.a[i][i] = 1;  // ② 单位矩阵
    while (e > 0) {
        if (e & 1) R = mul(R, A);     // ③ 累乘
        A = mul(A, A);                 // ④ 自乘
        e >>= 1;
    }
    return R;
}

逐行解析

  • ① 矩阵乘法三重循环。注意 k 放中间可以减少 cache miss。每次乘法后取模。
  • ② 单位矩阵 IIIii=1I_{ii} = 1,其余 0。I×A=AI \times A = A,是矩阵乘法的”1”。
  • ③④ 和普通快速幂结构完全一样——③ 累乘到结果,④ 底数自乘。

斐波那契的转移矩阵

K=2K=2 的特例,在 02-04 已经实现过。这里用通用模板:

// 斐波那契: f[n] = f[n-1] + f[n-2]
Mat M(2);
M.a[0][0] = 1; M.a[0][1] = 1;  // 第一行 = [1, 1]
M.a[1][0] = 1; M.a[1][1] = 0;  // 第二行 = [1, 0]

Mat R = power(M, N - 2);        // M^{N-2}
// f[N] = R.a[0][0] * f[2] + R.a[0][1] * f[1]
long long ans = (R.a[0][0] + R.a[0][1]) % MOD; // f[2]=1, f[1]=1

Tribonacci 的转移矩阵

K=3K=3,递推 an=an1+an2+an3a_n = a_{n-1} + a_{n-2} + a_{n-3}

Mat M(3);
M.a[0][0] = 1; M.a[0][1] = 1; M.a[0][2] = 1; // 第一行 = [1, 1, 1]
M.a[1][0] = 1; M.a[1][1] = 0; M.a[1][2] = 0; // 第二行 = [1, 0, 0]
M.a[2][0] = 0; M.a[2][1] = 1; M.a[2][2] = 0; // 第三行 = [0, 1, 0]

构造规则:第一行是递推系数 (c1,c2,c3)(c_1, c_2, c_3)。第 ii 行 (i2i \ge 2) 的第 i1i-1 列为 1,其余为 0——这行的效果是把 ani+1a_{n-i+1} “传递”给下一轮的状态向量。

例题

例题 1:M&A 054 — Fibonacci Hard(mod 10910^9

题目:斐波那契数列 f1=1,f2=1,fn=fn1+fn2f_1 = 1, f_2 = 1, f_n = f_{n-1} + f_{n-2}。求 fNmod109f_N \bmod 10^9

数据范围3N10183 \le N \le 10^{18}

—— AtCoder M&A 054

分析2×22 \times 2 矩阵快速幂。注意模数是 10910^9 不是 109+710^9+7

输入样例10输出55

代码

#include <cstdio>
#include <cstring>
using namespace std;

const long long MOD = 1000000000; // 注意:10^9 不是 10^9+7

struct Mat {
    long long a[2][2];
};

Mat mul(Mat A, Mat B) {
    Mat C; memset(C.a, 0, sizeof(C.a));
    for (int i = 0; i < 2; i++)
        for (int k = 0; k < 2; k++)
            for (int j = 0; j < 2; j++)
                C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD;
    return C;
}

Mat power(Mat A, long long e) {
    Mat R; memset(R.a, 0, sizeof(R.a));
    R.a[0][0] = R.a[1][1] = 1;                        // ① 单位矩阵
    while (e > 0) {
        if (e & 1) R = mul(R, A);
        A = mul(A, A);
        e >>= 1;
    }
    return R;
}

int main() {
    long long N;
    scanf("%lld", &N);

    Mat M; memset(M.a, 0, sizeof(M.a));
    M.a[0][0] = 1; M.a[0][1] = 1;                     // ② 转移矩阵
    M.a[1][0] = 1; M.a[1][1] = 0;

    Mat R = power(M, N - 2);                           // ③ M^{N-2}
    long long ans = (R.a[0][0] + R.a[0][1]) % MOD;    // ④ f[2]*M^{N-2}[0][0] + f[1]*M^{N-2}[0][1]
    printf("%lld\n", ans);
}

逐行解析

  • I=(1001)I = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}。快速幂的初始值。
  • M=(1110)M = \begin{pmatrix} 1 & 1 \\ 1 & 0 \end{pmatrix}
  • ③ 从 f2f_2 推到 fNf_N 需要乘 N2N-2 次。
  • fN=MN2[0][0]f2+MN2[0][1]f1=R[0][0]×1+R[0][1]×1f_N = M^{N-2}[0][0] \cdot f_2 + M^{N-2}[0][1] \cdot f_1 = R[0][0] \times 1 + R[0][1] \times 1

验证N=10N=10M8=(34212113)M^8 = \begin{pmatrix} 34 & 21 \\ 21 & 13 \end{pmatrix}f10=34+21=55f_{10} = 34 + 21 = 55。✓


例题 2:M&A 056 — Recurrence Formula 2(Tribonacci)

题目a1=1,a2=1,a3=2a_1 = 1, a_2 = 1, a_3 = 2an=an1+an2+an3a_n = a_{n-1} + a_{n-2} + a_{n-3}。求 aNmod109+7a_N \bmod 10^9+7

数据范围4N10184 \le N \le 10^{18}

—— AtCoder M&A 056

分析3×33 \times 3 矩阵快速幂。递推系数 (1,1,1)(1, 1, 1)

输入样例10输出149

代码

#include <cstdio>
#include <cstring>
using namespace std;

const long long MOD = 1000000007;

struct Mat {
    long long a[3][3];
};

Mat mul(Mat A, Mat B) {
    Mat C; memset(C.a, 0, sizeof(C.a));
    for (int i = 0; i < 3; i++)
        for (int k = 0; k < 3; k++)
            for (int j = 0; j < 3; j++)
                C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD;
    return C;
}

Mat power(Mat A, long long e) {
    Mat R; memset(R.a, 0, sizeof(R.a));
    R.a[0][0] = R.a[1][1] = R.a[2][2] = 1;            // ① 3×3 单位矩阵
    while (e > 0) {
        if (e & 1) R = mul(R, A);
        A = mul(A, A);
        e >>= 1;
    }
    return R;
}

int main() {
    long long N;
    scanf("%lld", &N);

    // ② 转移矩阵
    Mat M; memset(M.a, 0, sizeof(M.a));
    M.a[0][0] = 1; M.a[0][1] = 1; M.a[0][2] = 1; // a_n = a_{n-1} + a_{n-2} + a_{n-3}
    M.a[1][0] = 1; M.a[1][1] = 0; M.a[1][2] = 0; // a_{n-1} = a_{n-1}
    M.a[2][0] = 0; M.a[2][1] = 1; M.a[2][2] = 0; // a_{n-2} = a_{n-2}

    Mat R = power(M, N - 3);                           // ③ M^{N-3}

    // ④ a_N = R[0][0]*a_3 + R[0][1]*a_2 + R[0][2]*a_1
    long long ans = (R.a[0][0] * 2 + R.a[0][1] * 1 + R.a[0][2] * 1) % MOD;
    printf("%lld\n", ans);
}

逐行解析

  • ② 转移矩阵 3×33 \times 3。第一行 (1,1,1)(1,1,1) 是递推系数。
  • ③ 从 a3a_3 推到 aNa_N 需要乘 N3N-3 次。
  • ④ 初始状态向量 (a3,a2,a1)=(2,1,1)(a_3, a_2, a_1) = (2, 1, 1)aN=R[0][0]×2+R[0][1]×1+R[0][2]×1a_N = R[0][0] \times 2 + R[0][1] \times 1 + R[0][2] \times 1

验证a=(1,1,2,4,7,13,24,44,81,149,)a = (1, 1, 2, 4, 7, 13, 24, 44, 81, 149, \ldots)a10=149a_{10} = 149。✓


例题 3:M&A 055 — Recurrence Formula 1(含系数递推)

题目a1=1,a2=1a_1 = 1, a_2 = 1an=2an1+an2a_n = 2a_{n-1} + a_{n-2}。求 aNmod109+7a_N \bmod 10^9+7

数据范围3N10183 \le N \le 10^{18}

—— AtCoder M&A 055

分析:递推系数 (2,1)(2, 1)——不是 (1,1)(1, 1) 了。转移矩阵变成 (2110)\begin{pmatrix} 2 & 1 \\ 1 & 0 \end{pmatrix}。这个题在 02-04 例题 5 已经完整实现过。

代码(与 02-04 例题 5 相同):

#include <cstdio>
using namespace std;
const long long MOD = 1000000007;

struct Mat {
    long long a[2][2];
    Mat() { a[0][0]=a[0][1]=a[1][0]=a[1][1]=0; }
};

Mat mul(Mat A, Mat B) {
    Mat C;
    for (int i = 0; i < 2; i++)
        for (int k = 0; k < 2; k++)
            for (int j = 0; j < 2; j++)
                C.a[i][j] = (C.a[i][j] + A.a[i][k] * B.a[k][j]) % MOD;
    return C;
}

Mat power(Mat A, long long e) {
    Mat ans;
    ans.a[0][0] = ans.a[1][1] = 1;                     // ① 单位矩阵
    while (e > 0) {
        if (e & 1) ans = mul(ans, A);
        A = mul(A, A);
        e >>= 1;
    }
    return ans;
}

int main() {
    long long N;
    scanf("%lld", &N);
    Mat M;
    M.a[0][0] = 2; M.a[0][1] = 1;                     // ② [2, 1] 不是 [1, 1]
    M.a[1][0] = 1; M.a[1][1] = 0;
    Mat R = power(M, N - 2);                            // ③ M^{N-2}
    long long ans = (R.a[0][0] + R.a[0][1]) % MOD;     // ④ a_2=1, a_1=1
    printf("%lld\n", ans);
}

逐行解析

  • ② 关键区别:an=2an1+1an2a_n = 2a_{n-1} + 1 \cdot a_{n-2},所以转移矩阵第一行是 (2,1)(2, 1) 而非 (1,1)(1, 1)
  • ③④ 和斐波那契完全一样的流程——只要改转移矩阵。

验证a=(1,1,3,7,17,41,99,239,577,1393)a = (1, 1, 3, 7, 17, 41, 99, 239, 577, 1393)a10=1393a_{10} = 1393。✓


例题 4:M&A 049 — Fibonacci Easy(mod 109+710^9+7O(N)O(N) 递推)

题目:斐波那契数列 f1=1,f2=1f_1 = 1, f_2 = 1,求 fNmod109+7f_N \bmod 10^9+7

数据范围1N1061 \le N \le 10^6

—— AtCoder M&A 049

分析N106N \le 10^6O(N)O(N) 递推就够了,不需要矩阵快速幂。教材 5.3 B28 Fibonacci Easy 就是这么做的。但如果你已经写了矩阵快速幂模板,它也能轻松处理——两种方法都正确,选择取决于数据范围。

O(N)O(N) 代码

#include <cstdio>
using namespace std;
const int MOD = 1000000007;

int main() {
    int N;
    scanf("%d", &N);
    if (N <= 2) { printf("1\n"); return 0; }
    int a = 1, b = 1;                                  // ① f_1, f_2
    for (int i = 3; i <= N; i++) {
        int c = (a + b) % MOD;                         // ② f_i = f_{i-1} + f_{i-2}
        a = b;                                          // ③ 滚动:f_{i-2} ← f_{i-1}
        b = c;                                          // ④ f_{i-1} ← f_i
    }
    printf("%d\n", b);
}

逐行解析

  • ① 初始值 f1=1,f2=1f_1 = 1, f_2 = 1
  • ②③④ 滚动数组——只保留前两项,空间 O(1)O(1)
  • 复杂度 O(N)O(N)N=106N = 10^6 时约 10610^6 次加法,毫秒级。

什么时候用矩阵快速幂?N109N \ge 10^9 或递推阶数 KK 较小时。N106N \le 10^6O(N)O(N) 递推更简单更快。

参考文献

教材讲解 — 競技プログラミングの鉄則 第 5 章

基础练习 — アルゴリズムと数学 演習問題集


系列索引

第零章 基础工具

第一章 搜索技术

第二章 数学基础

第三章 数据结构

第四章 图论

第五章 动态规划

第六章 贪心

第七章 字符串

第八章 进阶