浅谈矩阵乘法
1.矩阵乘法
给出两个矩阵,令为的结果,若是一个的矩阵,是一个的矩阵,则是一个的矩阵。其中有:
注意到由于矩阵乘法的定义,只有当的列数等于的行数时才可以相乘,否则无意义。
柿子的定义可能比较抽象,难以理解,那我们就举个例子:
根据柿子手摸可以得到结果:
形象化地说,就是把的第行和的第列的个数一个个乘起来再相加。
参考代码:
#include <bits/stdc++.h>
using namespace std;
class Matrix {
public:
int n, m;
int val[55][55];
Matrix() { memset(val, 0, sizeof(val)); }
void init() {
for (int i = 1; i <= 50; ++i) {
for (int j = 1; j <= 50; ++j) {
val[i][j] = 1;
}
}
}
};
Matrix operator*(const Matrix &a, const Matrix &b) {
int i, j, k;
Matrix res;
for (k = 1; k <= a.m; ++k) {
for (i = 1; i <= a.n; ++i) {
for (j = 1; j <= b.m; ++j) {
res.val[i][j] += a.val[i][k] * b.val[k][j];
}
}
}
return res;
}
signed main() {
Matrix a, b, ans;
int n, m, p;
int i, j;
scanf("%d%d%d", &n, &m, &p);
a.n = n, a.m = m;
b.n = m, b.m = p;
for (i = 1; i <= n; ++i) {
for (j = 1; j <= m; ++j) {
scanf("%d", &a.val[i][j]);
}
}
for (i = 1; i <= m; ++i) {
for (j = 1; j <= p; ++j) {
scanf("%d", &b.val[i][j]);
}
}
ans = a * b;
for (i = 1; i <= n; ++i) {
for (j = 1; j <= p; ++j) {
printf("%d\n", ans.val[i][j]);
}
}
return 0;
}
2.矩阵快速幂
矩阵乘法满足结合律,即对于矩阵,有
所以我们可以对于矩阵进行经典快速幂运算。
这里要注意,矩阵乘法不满足交换律,即
参考代码:
#include <bits/stdc++.h>
using namespace std;
const int p = 1e9 + 7;
#define int long long
inline int read()
{
int ans = 0, f = 1;
char ch = getchar();
while (ch > '9' || ch < '0') {
if (ch == '-')
f = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
ans = (ans << 1) + (ans << 3) + (ch ^ '0');
ch = getchar();
}
return ans * f;
}
int n, k;
struct Matrix {
int a[105][105];
Matrix()
{
memset(a, 0, sizeof a);
}
inline void init()
{
for (int i = 1; i <= n; ++i)
a[i][i] = 1;
}
} t, ans;
Matrix operator*(const Matrix& x, const Matrix& y)
{
Matrix z;
for (int k = 1; k <= n; ++k)
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
z.a[i][j] = (z.a[i][j] + x.a[i][k] * y.a[k][j] % p) % p;
return z;
}
Matrix power(Matrix a, int b)
{
Matrix ans, base = a;
ans.init();
while (b) {
if (b & 1)
ans = ans * base;
base = base * base;
b >>= 1;
}
return ans;
}
signed main()
{
n = read(), k = read();
register int i, j;
ans.init();
for (i = 1; i <= n; ++i) {
for (j = 1; j <= n; ++j) {
t.a[i][j] = read();
}
}
ans = power(t, k);
for (i = 1; i <= n; ++i) {
for (j = 1; j <= n; ++j)
printf("%lld ", ans.a[i][j]);
puts("");
}
return 0;
}
3.应用
我们来看一个经典例题:
给出,求斐波那契数列的第项mod 998244353
暴力递推显然满足不了的数据范围,所以我们需要加速这个过程。
加速的方法很多,比如特征方程求通项公式等数学方法;这里给出矩阵快速幂的解法。
我们构造一个矩阵:
其中表示斐波那契数列的第项。
我们把这个矩阵乘上一个base
矩阵:
得到:
即:
发现什么?我们通过一次矩阵乘法的运算即得到了斐波那契数列的下一项,完成了一次递推。同时,矩阵可以进行快速幂,所以我们可以通过乘上base
矩阵的次幂来快速递推,复杂度被优化到了
到这里可以发现,矩阵快速幂可以加速递推式的计算过程。
我们再次观察上述求解过程:到底是怎么构造出原矩阵和base
矩阵的呢?
斐波那契数列的通项公式为:,所以我们要知道必须要知道和。因为矩阵乘法是两个矩阵内部的运算,不可能凭空出现一个新的值,所以我们肯定要在原矩阵里面记录下这两个值才能得到。
所以现在我们有矩阵,要乘上一个base
矩阵之后得到,又
将系数联立起来即可得到base
矩阵。
4.例题
1.洛谷P1939【模板】矩阵加速(数列)
给出,已知,求
我们构造矩阵,希望得到结果为
显然有:
所以构造出base
矩阵:
2.[NOI2012] 随机数生成器
给出,已知,求
这里发现只与有关,所以我们构造出原始矩阵:
显然有:
所以我们乘上:
即可得到:
这道题目原题好像裸的矩阵快速幂会炸longlong要加上龟速乘,不过这跟无辜的矩阵有什么关系呢
3.LOJ#10222 佳佳的 Fibonacci
设为斐波那契数列的第项,,求
我们发现
所以我们可以根据这个递推式构造矩阵:
因为
所以我们得到base
矩阵为
将原矩阵乘上base
矩阵即可得到:
4.[HNOI2011]数学作业
定义为从到的所有整数写在一起得到的数,如,求
显然有,其中表示的位数,所以可以就此进行矩阵加速递推。
原矩阵:
base
矩阵:
但是我们发现不好维护,考虑到,所以的值只有18种左右,枚举位数分开矩乘即可。
5.[SNOI2017]礼物
设,求
先推推柿子:
这样我们得到了的递推式,尝试矩阵优化;但是我们发现不好直接维护,考虑二项式定理:
于是便可以构造矩阵:
我们希望得到
于是便可以推出base
矩阵:
这里定义
注意到非常小,所以以上做法可以通过。
6.[THUSCH2017] 大魔法师
给出个元素,每个元素有个值:,共进行次操作,每次选取一个区间进行如下几种操作之一:
求
请对于每个操作输出结果
我们把每个元素看成一个矩阵:
显然每次操作可以用矩阵乘法来表示,例如操作的base
为:
操作同理
我们对于这个矩阵开一棵线段树,显然操作即为区间乘,操作为区间求和,线段树都可以完成。
注意到矩阵存在分配律,所以可以用懒标记实现。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步