「学习笔记」矩阵快速幂
一.快速幂#
P1226 【模板】快速幂||取余运算#
快速幂用来解决这样的式子 ,大家应该都会,我就放个代码。
ll ksm (ll a, ll b) {//a^b
ll res = 1;
while (b) {
if (b & 1) {
res = res * a;
}
b >>= 1;
a = a * a;
}
return res;
}
Ybtoj【例题1】序列的第k个数#
这道题就可以直接根据前三项判断是等差数列还是等比数列。
等差数列 ,等比数列 , 为公差。
二.矩阵#
概念1.单位矩阵#
主对角线上的元素都为 ,其余元素都为 的 阶矩阵称为 阶单位矩阵。
单位矩阵的重要点在于:单位矩阵的任意次方都等于它本身。
在矩阵快速幂转移时,可以将其设为初始矩阵。
概念2.矩阵加/减法#
两个矩阵相加/减,就将其对应位置上的数相加/减。
设 为长宽相等的矩阵,矩阵 ,那么 。
下图就是一个加法的例子。
概念3.矩阵乘法#
设矩阵 ,有几个要求。
的列数等于 的行数。
是 的矩阵, 是 的矩阵,那么 就是 的矩阵。
。
我们显然可以得到一个结论:
矩阵乘法满足结合律,不满足交换律。
这里给出 的矩阵乘法代码:
inline void Mul (int f[][], int x[][], int y[][]) {//计算矩阵x乘y=f。
int t[N][N];//临时记录答案矩阵。
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
t[i][j] = 0;//清空答案矩阵。
}
}
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
for (int k = 0; k < n; k ++) {
t[i][j] = t[i][j] + x[i][k] * y[k][j];//矩阵乘法法则。
}
}
}
for (int i = 0; i < n; i ++) {
for (int j = 0; j < n; j ++) {
f[i][j] = t[i][j];//复制答案。
}
}
}
概念4.方阵乘幂#
方阵是一个长和宽相等的矩阵。
将方阵 自乘 次,记作 。
上文说到矩阵乘法满足结合律,那么我们就可以将方阵乘幂利用快速幂的思想优化。
三.矩阵快速幂的引入#
P3390 【模板】矩阵快速幂#
先解决这道模板题。
给定矩阵 ,求 。
只需要将快速幂中的数字变为矩阵即可,乘法变为矩阵乘法。
给出一个模板,这是以后做题的基础。
inline void qpow (int T) {
initans ();//这里是将ans初始化为单位矩阵。
while (T) {
if (T & 1) {
Mul (ans, ans, a);
//ans=ans*a。
}
Mul (a, a, a);
//a=a*a。
T >>= 1;
}
}
观察一下,是不是与原来的快速幂模板一模一样?
Ybtoj【例题2】斐波那契数列#
P1962 斐波那契数列#
求斐波那契数列的第 项 。
递推显然不可做,我们就需要采用矩阵快速幂来进行优化。
我们的思路在于:
我们将最后求的答案变为一个矩阵 ,构建一个初始矩阵 和一个转移矩阵 ,使 。
那么,我们设答案矩阵 ,我们希望通过 得到 ,也就是说让 。
由 得:
转移矩阵的第一列为 ,因为 。
那么可以推出转移矩阵的第二列为 ,因为 。
那么转移矩阵就是 。
因为 ,那么设初始矩阵 ,乘一次 都能计算一项,也就是说我们要从 推到 需要乘 次 。
也就说 。
核心代码如下:
inline void qpow (int b) {
while (b) {
if (b & 1) {
Mul (f, f, a);///计算f=f×a。
}
Mul (a, a, a);//a=a^2。
b >>= 1;
}
}
最后输出 ,答案矩阵的第一位就是 。
这里的矩阵快速幂仅仅是把快速幂中的数字改为了矩阵而已。
P1939 【模板】矩阵加速(数列)#
设答案矩阵为 ,有一个矩阵 ,我们要构建矩阵 使 。
然后可以推出矩阵 。
套模板即可。
四.矩阵快速幂的运用#
P1349 广义斐波那契数列#
易得转移矩阵 ,初始矩阵 ,套矩阵快速幂模板转移 次即可。
P2044 [NOI2012] 随机数生成器#
题目给出了递推式,发现要加上 ,我们的初始矩阵就设为 。
然后就根据递推式得出转移矩阵 ,矩阵快速幂 次转移即可。
但是我们发现数据非常大,那么就要使用龟速乘。
Ybtoj【例题3】行为方案#
P3758 [TJOI2017]可乐#
P5789 [TJOI2017]可乐(数据加强版)#
发现这个自爆操作不太好搞,可以创建一个虚拟点 ,自爆操作就相当于走到 点。
可以设出一个状态 为 走到 走 步的方案数,那么就有一个暴力方程 。
然后我们发现这个第三维可以压掉,那么 。
这样的复杂度是 的,无法通过怎么办?
发现这个转移方程非常符合矩阵快速幂的形式,我们可以用矩阵快速幂优化到 。
最后的答案就是 。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
const int mod = 2017;
int n, m, t, base[N][N], ans[N][N];
inline void Mul (int f[N][N], int x[N][N], int y[N][N]) {
int t[N][N];
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= n; j ++) {
t[i][j] = 0;
}
}
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= n; j ++) {
for (int k = 0; k <= n; k ++) {
t[i][j] = (t[i][j] + x[i][k] * y[k][j] % mod) % mod;
}
}
}
for (int i = 0; i <= n; i ++) {
for (int j = 0; j <= n; j ++) {
f[i][j] = t[i][j];
}
}
}
inline void qpow (int T) {
while (T) {
if (T & 1) {
Mul (ans, ans, base);
}
Mul (base, base, base);
T >>= 1;
}
}
int main () {
scanf ("%d%d", &n, &m);
for (int i = 1; i <= m; i ++) {
int u, v;
scanf ("%d%d", &u, &v);
base[u][v] = 1;
base[v][u] = 1;//有连边的方案为1。
}
for (int i = 0; i <= n; i ++) {
base[i][i] = 1;//自己到自己方案为1。
base[i][0] = 1;//到爆炸点方案为1。
ans[i][i] = 1;//初始矩阵设为单位矩阵。
}
scanf ("%d", &t);
qpow (t);//因为初始的时刻为0,所以总共要转移t次。
int sum = 0;
for (int i = 0; i <= n; i ++) {
sum = (sum + ans[1][i]) % mod;
}
printf ("%d", sum);
return 0;
}
代码中初始矩阵和答案矩阵是同一个矩阵,因为我在快速幂时不断覆盖初始矩阵。
初始矩阵因为是没操作的情况,那么就设为单位矩阵。
Ybtoj【例题4】矩阵求和#
这题我就稍微讲一下思路。(因为我也不太会做)
我们构造矩阵 ,其中 为给出的初始矩阵, 为 的单位矩阵, 为 的零矩阵。
那么 ,就发现 右上角的矩阵减去一个单位矩阵就是我们所要求的的答案。
Ybtoj【例题5】最短路径#
P2886 [USACO07NOV]Cow Relays G#
我们发现,有 条边,但有 个点,那我们先离散化一下。
抛开边数限制,我们可以采用 算法。
图的邻接矩阵存储本质上就是一个矩阵,那我们可以想到用矩阵快速幂优化。
由于矩阵乘法满足结合律,我们直接转化,将乘法变为取 。
每次乘幂都是相当于走了一条边,那么乘 次转移矩阵就相当于走了 条边。
模板转化即可。
inline void Mul (int f[N][N], int x[N][N], int y[N][N]) {
int t[N][N];
for (int i = 0; i < tot; i ++) {
for (int j = 0; j < tot; j ++) {
t[i][j] = inf;//floyd算法初始为inf。
}
}
for (int i = 0; i < tot; i ++) {
for (int j = 0; j < tot; j ++) {
for (int k = 0; k < tot; k ++) {
t[i][j] = min (t[i][j], x[i][k] + y[k][j]);//将乘法改为min。
}
}
}
for (int i = 0; i < tot; i ++) {
for (int j = 0; j < tot; j ++) {
f[i][j] = t[i][j];
}
}
}
多说一句,由于 上的数据有问题,所以有一个点需要数据特判才能 。
P2233 [HNOI2002]公交车路线#
这种题一看就是线性递推,那么就容易想到用矩阵快速幂进行优化。
阅读题面先写出几个信息:
为偶数时方案数为 。
时方案数为 。
那么我们先特判 ,然后把 除以二,这样每一个数字都是相邻的,便于处理。
我们可以马上秒一个 爆搜,得到前几位:
。
猜测 与 和 有关系,那我们列方程组,解得递推式如下:
。
设答案矩阵为 ,那么这样构建初始矩阵 ,转移矩阵 。
然后转移 次就可以了。
答案要乘 ,因为我们只计算走向一边的方案。
又因为转移矩阵中有负数,所以我们要在答案输出时防止出现负数,像这样操作:
cout << (2 * ans[0][0] % mod + mod) % mod;
完整代码不放了就套模板即可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!