【精选】矩阵加速
大家好,我是Weekoder!
今天要讲的内容是矩阵加速!
这时候就有人说了:
不不不,容我解释。在经过我的研究后,我发现基本的矩阵运算和矩阵加速都并没有那么难。只要继续往下看,相信你也能学会!
注意:以下内容的学习难度将会用颜色表示,与洛谷题目难度顺序一致,即
矩阵和二维数组很像,是由
可以看到,矩阵中的每个元素都有着对应的行和列,我们把一个矩阵记作
其中
元素全部为
零矩阵记作
只有主对角线上的元素有值,其余元素为
注:主对角线为矩阵中从左上角到右下角的一条对角线。
对角矩阵根据主对角线上的值,记作
主对角线上的元素均为
单位矩阵记作
记得分数中的概念分数单位吗?矩阵单位和分数单位的“地位”差不多,代表的都是最基础的,最小的独立个体。你可以把单位矩阵看做数字
最基础的,常见的特殊矩阵就是这些了。当然,还有很多的特殊矩阵,不过我们暂时用不到。
若对于矩阵
其实就是两个矩阵长得一模一样。
若要求
总结一句话:对应位置相加。
矩阵加法满足交换律和结合律:
减法同理,对应位置相减。
数
还是一句话:对应位置相乘。
虽然矩阵乘法也属于矩阵运算,但难度比前面的都高,而且是今天的重点内容,所以单独放出来讲,故记为
上例题!(虽然难度是橙)
先看矩阵乘法的定义:若有
只要枚举
我知道,这看起来根本不是新手蒟蒻能看懂的。那我就用人话来讲讲矩阵乘法。
矩阵乘法并不是一个一个乘,而是行对应列乘。怎么个乘法呢?我们来看看下面两个矩阵相乘的例子。
第一个矩阵为
我们先取出
再取出
不对,你给我转过来。
现在终于可以相乘了。逐位相乘得出结果:
得出了结果
还记得我们之前是怎么取的吗?我们取了
最后,可以看看代码辅助理解。
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
int n, m, k, a[N][N], b[N][N]; // 用二维数组存矩阵 A,B
int main() {
cin >> n >> m >> k;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
cin >> a[i][j]; // 输入矩阵 A
for (int i = 1; i <= m; i++)
for (int j = 1; j <= k; j++)
cin >> b[i][j]; // 输入矩阵 B
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= k; j++) { // 枚举 C 矩阵 n 行 k 列的每个元素
// 以下部分为模拟刚刚讲的矩阵乘法
int sum = 0; // 求和,sum 即为 C_ij
for (int l = 1; l <= m; l++)
sum += a[i][l] * b[l][j]; // 求和,A 的行和 B 的列,建议模拟一下过程加强理解
cout << sum << " "; // 输出 sum(C_ij)
}
cout << "\n"; // 记得换行!
}
return 0; // 完美的结束
}
这样就能愉快地切掉这道题了。请完成这道题再继续!
矩阵乘法满足以下性质:
结合律:
分配律:
矩阵乘法不满足交换律。(这是重点!)
有了矩阵乘法,我们还可以结合上面的特殊矩阵得到一些性质:
快到今天的主题了!上例题!
点开题目后的你 be like:
这是啥呀?
我来让题目描述“缩点水”:
给定一个
行 列的矩阵 ,求 ,即 。
第一思路:暴力!直接做
考虑放弃做题。
那我们该怎么优化呢?看到需要计算 *
乘号就行了呢?
注意:不会快速幂的话可以先简单看看我写的文章。
回到主题,有没有什么办法能只要写一个 *
乘号就能进行矩阵乘法呢?其实我们可以用结构体把矩阵封装起来,再用重载运算符就行了。关于重载运算符,可以参考这些资料。
定义一个矩阵类型的结构体可以写成这样:
struct Matrix {
};
我们需要在里面用一个二维数组存储矩阵。我们还可以写一个结构体初始化函数,只要定义了一个矩阵,就自动清零,免去清零的麻烦。
struct Matrix {
int a[N][N]; // N 为矩阵大小
Matrix() {
memset(a, 0, sizeof a);
}
};
最后,把矩阵乘法写进去。
struct Matrix {
ll a[N][N];
Matrix() {
memset(a, 0, sizeof a);
}
Matrix operator*(const Matrix &x)const {
Matrix res;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
return res;
}
};
注意,这里一定要写成 a[i][k] * x.a[k][j]
,不能写成 x.a[i][k] * a[k][j]
,因为矩阵乘法不满足交换律!
这样,结构体封装部分就完成了。
我们要定义两个矩阵:
void init() {
for (int i = 1; i <= n; i++) base.a[i][i] =1;
}
初始化完以后,就可以执行快速幂了,计算
void expow(ll b) {
while (b) {
if (b & 1) base = base * a;
a = a * a, b >>= 1;
}
}
有一点需要注意的就是,不能写成 base *= a
等形式,因为重载运算符定义的是 *
,没有定义 *=
,所以需要将 *=
展开。
最后,就可以输出
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 105, MOD = 1e9 + 7;
int n;
ll k;
struct Matrix {
ll a[N][N];
Matrix() {
memset(a, 0, sizeof a);
}
Matrix operator*(const Matrix &x)const {
Matrix res;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
for (int k = 1; k <= n; k++)
res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
return res;
}
}a, base;
void init() {
for (int i = 1; i <= n; i++) base.a[i][i] =1;
}
void expow(ll b) {
while (b) {
if (b & 1) base = base * a;
a = a * a, b >>= 1;
}
}
int main() {
cin >> n >> k;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
cin >> a.a[i][j];
init();
expow(k);
for (int i = 1; i <= n; putchar('\n'), i++)
for (int j = 1; j <= n; j++)
cout << base.a[i][j] << " ";
return 0;
}
终于到了最后的
点击此处进入
点开题目
这和矩阵有什么关系吗???
我直接一个递推!
- 对于
的数据 , 。
(呜呜呜我再也不学 c艹 了)
没关系,先看看思路!
因为发现当
显然,这三个元素都是
那么,假设我想要得到
更加通用一点:
可以发现,矩阵中的每个元素的项数都向前推进了
如果
输出
否则
执行运算
并输出答案矩阵
特判(对于特殊情况的判断)和输出应该没什么问题,主要是为什么运算恰好要执行
还是假设要获取
变为
这样就刚好在第
那么,说了这么久,这个神秘的运算是什么呢?当当当当~,他就是我们的——矩阵乘法!
没错,所谓的变换,其实就是乘上了一个特殊的矩阵!那么,这个矩阵长什么样呢?让我们一起来推理吧。
(此处应配上推理の小曲)
我们可以先列一个表格,表格的行代表矩阵
好了,对于
观察系数
后面的也以此类推:
这样,我们就可以推出这个神秘的矩阵了:
好了,现在我们终于知道了,一次神秘操作,就是将让
一次矩阵乘法的时间复杂度还没有递推快,这根本就没有优化嘛。
等等!我们把这个式子展开:
不是吧!这居然变成了一个矩阵快速幂?!!
也就是说,我们可以用快速幂计算
下面奉上代码:(标准的矩阵加速思想)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MOD = 1e9 + 7;
int T, n;
struct Matrix {
ll a[5][5];
Matrix() {
memset(a, 0, sizeof a);
}
Matrix operator*(const Matrix &x)const { // 矩阵乘法
Matrix res;
for (int i = 1; i <= 3; i++)
for (int j = 1; j <= 3; j++)
for (int k = 1; k <= 3; k++)
res.a[i][j] = (res.a[i][j] % MOD + a[i][k] % MOD * x.a[k][j] % MOD) % MOD;
return res;
}
void mems() {
memset(a, 0, sizeof a);
}
}ans, base;
void init() { // 初始化两个矩阵
ans.mems(), base.mems(); // 记得清空!
ans.a[1][1] = ans.a[1][2] = ans.a[1][3] = 1;
base.a[1][1] = base.a[1][2] = base.a[2][3] = base.a[3][1] = 1;
}
void expow(int b) { // 矩阵快速幂,是在 ans 矩阵的基础上乘的
while (b) {
if (b & 1) ans = ans * base;
base = base * base, b >>= 1;
}
}
int main() {
cin >> T;
while (T --) {
cin >> n;
init(); // 初始化不能忘
if (n <= 3) { // 特判
cout << "1\n";
continue;
}
expow(n - 3); // 计算特殊矩阵的 n - 3 次方,已经乘到了 ans 里
cout << ans.a[1][1] << "\n"; // 输出答案!芜湖!
}
return 0; // 快乐结束
}
就这样,我们完成了矩阵加速递推。
再次声明矩阵快速幂(矩阵加速)时间复杂度:
小提示:关于 矩阵的构造
就是这个
可以这样:我们要推导出
那么,能不能构造一个行列数各不相同的矩阵,而不是一个
可以看到,左边
这篇文章花费了我很多时间,希望你喜欢!
对了,你学会了吗?是不是,矩阵也并没有那么难?
这应该是我的【精选】文章中的第一篇,没想到写的是矩阵方面的。
总之,很感谢你的阅读!希望你能从我这学到点东西!
再见!
本文作者:Weekoder
本文链接:https://www.cnblogs.com/Weekoder/p/18237764
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)