前言
02-04 快速幂与逆元 里已经介绍过矩阵快速幂的基本原理:把递推式 写成矩阵形式,然后用快速幂在 内求出第 项。那篇的例题 5 给出了一个 矩阵的完整实现。
这篇是矩阵快速幂的进阶篇。我们会处理更高阶的递推(Tribonacci 三阶递推、 阶递推),以及更复杂的转移矩阵构造方法。核心思想不变:任何常系数线性递推都可以写成矩阵乘法。
问题的本质
从递推到矩阵
斐波那契递推 可以写成:
展开验证:,。✓
递推 次就得到:
其中 。 用快速幂 算出。
高阶递推的推广
三阶递推 (Tribonacci)的转移矩阵是 :
规律:转移矩阵的第一行就是递推系数(),其余行是单位向量偏移(把第 行第 列设为 1,用来”向下移动”状态)。
一般地, 阶常系数线性递推 对应 转移矩阵:
复杂度
矩阵乘法 ,快速幂 次乘法,总时间 。 约 次乘法,瞬间跑完。
理论 + 代码
通用矩阵快速幂模板
#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。每次乘法后取模。 - ② 单位矩阵 :,其余 0。,是矩阵乘法的”1”。
- ③④ 和普通快速幂结构完全一样——③ 累乘到结果,④ 底数自乘。
斐波那契的转移矩阵
的特例,在 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 的转移矩阵
,递推 :
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]
构造规则:第一行是递推系数 。第 行 () 的第 列为 1,其余为 0——这行的效果是把 “传递”给下一轮的状态向量。
例题
例题 1:M&A 054 — Fibonacci Hard(mod )
题目:斐波那契数列 。求 。
数据范围:
分析: 矩阵快速幂。注意模数是 不是 。
输入样例: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);
}
逐行解析:
- ① 。快速幂的初始值。
- ② 。
- ③ 从 推到 需要乘 次。
- ④ 。
验证:,。。✓
例题 2:M&A 056 — Recurrence Formula 2(Tribonacci)
题目:,。求 。
数据范围:
分析: 矩阵快速幂。递推系数 。
输入样例: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:M&A 055 — Recurrence Formula 1(含系数递推)
题目:,。求 。
数据范围:
分析:递推系数 ——不是 了。转移矩阵变成 。这个题在 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);
}
逐行解析:
- ② 关键区别:,所以转移矩阵第一行是 而非 。
- ③④ 和斐波那契完全一样的流程——只要改转移矩阵。
验证:。。✓
例题 4:M&A 049 — Fibonacci Easy(mod , 递推)
题目:斐波那契数列 ,求 。
数据范围:
分析:, 递推就够了,不需要矩阵快速幂。教材 5.3 B28 Fibonacci Easy 就是这么做的。但如果你已经写了矩阵快速幂模板,它也能轻松处理——两种方法都正确,选择取决于数据范围。
代码:
#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);
}
逐行解析:
- ① 初始值 。
- ②③④ 滚动数组——只保留前两项,空间 。
- 复杂度 。 时约 次加法,毫秒级。
什么时候用矩阵快速幂? 当 或递推阶数 较小时。 时 递推更简单更快。
参考文献
教材讲解 — 競技プログラミングの鉄則 第 5 章
基础练习 — アルゴリズムと数学 演習問題集
- 049 Fibonacci Easy( 递推 mod )【例题】
- 054 Fibonacci Hard(矩阵快速幂 mod )【例题】
- 055 Recurrence Formula 1(含系数 矩阵快速幂)【例题】
- 056 Recurrence Formula 2(Tribonacci 矩阵快速幂)【例题】
- 062 Teleporter(倍增/矩阵快速幂 )
系列索引
第零章 基础工具
第一章 搜索技术
第二章 数学基础
第三章 数据结构
- 03-01 栈、队列与单调栈
- 03-02 堆与优先队列
- 03-03 并查集
- 03-04 树状数组
- 03-05 线段树
- 03-06 懒标记线段树
- 03-07 Sparse Table 与倍增
- 03-08 字符串哈希
第四章 图论
- 04-01 图的遍历
- 04-02 最短路—Dijkstra 与 01-BFS
- 04-03 最短路—Bellman-Ford 与 Floyd
- 04-04 拓扑排序
- 04-05 最小生成树
- 04-06 强连通分量与 2-SAT
- 04-07 二分图与网络流
- 04-08 树上问题
第五章 动态规划
- 05-01 DP入门—状态与转移
- 05-02 背包问题族
- 05-03 LIS、LCS与编辑距离
- 05-04 区间DP
- 05-05 状态压缩DP
- 05-06 树形DP与数位DP
- 05-07 矩阵快速幂与线性递推
第六章 贪心
第七章 字符串
第八章 进阶