其实这个东西我一直想写,结果不知不觉间就拖了一个月了,必须得写了,再不写就忘了。
是什么
一句话:矩阵就是"按长方形排列的一堆数"。
矩阵 时由 \(m\times n\) 个数按 \(m\) 行 \(n\) 列排成的矩形阵列,记作 \(A=(a_{ij})\),其中 \(a_{ij}\) 表示第 \(i\) 行第 \(j\) 列的元素。
本质 :线性方程组的系数表、线性变换的表示、数据的矩形组织形式。
就像这样:
text
┌ ┐
│ 1 2 3 │ ← 第1行
│ 4 5 6 │ ← 第2行
└ ┘
↑ ↑ ↑
第1列 第2列 第3列
这矩阵有两种最基础的运算方法:加法 和乘法。
矩阵加法
先说简单的,这个就是两个矩阵的元素做加法。
怎么加呢?
举个例子:很简单------把相同位置的数字逐个相加 ,得到的新矩阵就是结果。
但是 ,两个相加的矩阵大小必须完全一样。
A=
[1 2 3]
[4 5 6]
B=
[7 8 9]
[10 11 12]
过程
- 第1行第1列:1 + 7 = 8
- 第1行第2列:2 + 8 = 10
- 第1行第3列:3 + 9 = 12
- 第2行第1列:4 + 10 = 14
- 第2行第2列:5 + 11 = 16
- 第2行第3列:6 + 12 = 18
结果 C=
[8 10 12]
[14 16 18]
矩阵乘法
这东西就讲究的多了,还要先看能不能乘。傲娇的乘法。
有个大前提:左边矩阵的列数 = 右边矩阵的行数。
- 若 \(A\) 是 \(n \times \color{red}{m}\) 的矩阵(\(n\)行\(\color{red}{m}\)列)
- 那么 \(B\) 必须是 \(\color{red}{m}\) \(\times p\) 的矩阵(\(\color{red}m\)行\(p\)列)
- 结果矩阵 \(C\) 的大小是 \(n \times p\)(\(n\)行\(p\)列)
如果 \(A\) 的列数和 \(B\) 的行数不相等,那它们俩就没法相乘("无意义")。
再看看怎么乘的:
具体操作:
- 拿出 \(A\) 的第 \(i\) 行所有数字;
- 拿出 \(B\) 的第 \(j\) 列所有数字;
- 将它们一一对应相乘 ,然后把所有乘积加起来 的到答案 C中的 \(c_{i,j}\)。
这个乘法对答案还是有影响的:对矩阵大小有影响。
这下知道为什么了吧,其实重点就是在看能不能乘上面。要是不能乘还硬乘,想不无意义都难!
举个栗子:
假设有矩阵 \(A\)(\(2\)行\(3\)列)和 \(B\)(\(3\)行\(2\)列):
-
\(A\) =
[1 2 3][4 5 6] -
\(B\) =
[7 8][9 10][11 12]
因为 \(A\) 的列数(\(3\))等于 \(B\) 的行数(\(3\)),所以可以乘。结果 \(C\) 是 \(2\)行\(2\)列。
计算过程
-
C 的第1行第1列 = \(A\)的第1行
(1,2,3)乘以 \(B\) 的第1列(7,9,11)\(= 1×7 + 2×9 + 3×11 = 7 + 18 + 33 = 58\)
-
C 的第1行第2列 = \(A\)的第1行
(1,2,3)乘以 \(B\) 的第2列(8,10,12)\(= 1×8 + 2×10 + 3×12 = 8 + 20 + 36 = 64\)
-
C 的第2行第1列 = \(A\)的第2行
(4,5,6)乘以 \(B\)的第1列(7,9,11)\(= 4×7 + 5×9 + 6×11 = 28 + 45 + 66 = 139\)
-
C 的第2行第2列 = \(A\)的第2行
(4,5,6)乘以 \(B\)的第2列(8,10,12)\(= 4×8 + 5×10 + 6×12 = 32 + 50 + 72 = 154\)
所以,结果 C =
[58 64]
[139 154]
注意
- 不满足交换律 :绝大多数情况下,\(A\times B \ne B\times A\)(甚至 \(B×A\) 可能根本乘不了)。
- 顺序很重要 :公式里永远是 "左矩阵的行" 去乘 "右矩阵的列",不能反过来。
- 数字对应要齐:行和列的元素个数必须相同,否则无法对应相乘。
代码
这里直接把矩阵加法、乘法和快速幂一起放出来。
其实矩阵是支持快速幂的,就是一个矩阵的几次方:\(A^{k}\),就是\(k\)个\(A\)相乘。
PID:P3390
cpp
#include<bits/stdc++.h>
#define int long long
const int M=1e9+7;
using namespace std;
struct Matrix{
int matrix[110][110],n,m;
Matrix(int _n,int _m){
n=_n,m=_m;
memset(matrix,0,sizeof matrix);
return ;
}
int* operator [](int i){return matrix[i];}
void init(){
memset(matrix,0,sizeof matrix);
for(int i=1;i<=n;i++) matrix[i][i]=1;
return ;
}
void print(){
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++) cout<<matrix[i][j]<<" ";
cout<<"\n";
}
}
};
Matrix operator +(Matrix a,Matrix b){//加
for(int i=1;i<=a.n;i++)
for(int j=1;j<=a.m;j++) a[i][j]+=b[i][j];
return a;
}
Matrix operator *(Matrix &a,Matrix &b){//乘
Matrix c(a.n,b.m);
for(int i=1;i<=a.n;i++)
for(int j=1;j<=b.m;j++)
for(int k=1;k<=a.m;k++) (c[i][j]+=a[i][k]*b[k][j]%M)%=M;
return c;
}
Matrix operator %(Matrix a,int mod){//模
for(int i=1;i<=a.n;i++)
for(int j=1;j<=a.n;j++) a[i][j]%=mod;
return a;
}
Matrix operator ^(Matrix a,int b){//快速幂
Matrix c(a.n,a.n);
c.init();
while(b){
if(b&1) c=c*a%M;
a=a*a%M;
b>>=1;
}
return c;
}
int n,b;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0),cout.tie(0);
cin>>n>>b;
Matrix a(n,n);
for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>a[i][j];
a=a^b;
a.print();
return 0;
}
另外
有一些比较特殊的矩阵:
-
零矩阵 (O) :全 \(0\) 矩阵,加法单位元,A+O=A。
栗子
text[0,0,0] [0,0,0] -
单位矩阵 I(或 E) :方阵 ,主对角线为 1、其余为 0,乘法单位元,A\(\times\)I=I\(\times\)A=A。(然后你会发现这个满足交换律)
栗子 \\times 2
text[1 0 0] [0 1 0] [0 0 1] -
其他特殊矩阵 :对角矩阵、对称矩阵(Aᵀ=A)、三角矩阵、行/列向量、转置矩阵\(\dots\)
有什么用
说这么多,其实在OI的题目中最大的用处就是 "批量转移加速器" 。
比如说一道dp的,通常是线性递推加速 ,那就可以用矩阵快速幂优化。
图论中,跑图也是一样的,用矩阵快速幂优化。