矩阵

在数学中,矩阵是一种很重要的表现形式;在OI中,矩阵也是一种很重要的数据结构。
矩阵通常可以用在线性齐次递推式的加速上,比如说加速斐波那契数列的递推过程。
矩阵还可以让多个数据关联起来,并简便地进行区间维护。

基本概念

为了更好地区分矩阵与其他量,我们这里将代表矩阵的符号进行了特殊处理,就像这个样子:

AA

矩阵是什么

n×m 个元素组成的,形如

A=[a1,1a1,2a1,3a1,ma2,1a2,2a2,3a2,ma3,1a3,2a3,3a3,man,1an,2an,3an,m]

nm 列的数表,我们称之为大小为 n×m 的矩阵,可以简记为 An×m

特殊的矩阵

特殊的矩阵由很多种,比如单位矩阵、上三角矩阵等等。

行矩阵与列矩阵

行矩阵就是只有一行的矩阵,列矩阵就是只有一列的矩阵。

零矩阵

元素全为 0 的矩阵。
零矩阵简记为 0

负矩阵

对于一个矩阵 A 的负矩阵 A,其中每个元素都与矩阵 A 内相同位置的元素互为相反数。

方阵

方阵指的就是正方形的矩阵,其行数与列数相等。
此时,其行数(或列数,反正他们相等)就可以被称作该矩阵的阶。

简单来说,一个 n 阶方阵 An 其实就是相当于一个 n×n 的矩阵 An×n

单位矩阵

单位矩阵就是指,在主对角线上的元素都是 1,其余元素都为 0 的矩阵。
主对角线就是指 (1,1)(n,n)。这也说明单位矩阵都是正方形的。

单位矩阵简记为 I

单位矩阵也有其相应的阶,比如1到4阶的单位矩阵分别是这个样子的:I1=[1]I2=[11]I3=[111]I4=[1111]

形象一点,就是这样:
(为 0 的元素太多时一般将其省略)

I=[111]

单位矩阵的一个很重要的性质就是,任何矩阵乘以单位矩阵的结果还是其本身,即 AI=IA=A

有时候还可能将单位矩阵称为 E,但是很少见,我目前还没有见过。

矩阵的运算

矩阵可以进行四则运算,但是与正常的数字还是有所不同。

加减

矩阵的加减要求两个矩阵必须行数列数均相同才可以进行。

加减的时候,每一对位置相同的元素相加或者相减。

形象一点就是这个样子:

A±B=[abcdef]±[ghijkl]=[a±gb±hc±id±je±kf±l]

矩阵的加法满足交换律和结合律。

数乘

就是一个矩阵乘以一个数。

乘起来的时候,矩阵内的每一个元素都要乘以这个数。

形象一点就是这样:

λA=λ×[abcdef]=[λaλbλcλdλeλf]

矩阵的数乘满足交换律、结合律和分配律。

如果将刚才这个过程反过来的话,就叫做矩阵提公因子。

点乘

点乘是矩阵运算中很重要的一部分。

点乘又叫做矩阵乘法,(基本上)是OI中矩阵的精髓。

矩阵乘法不满足交换律,是因为其需要满足左侧矩阵的列数与右侧矩阵的行数相等。

形象一点,就是 An×p×Bp×m=Cn×m

具体操作的时候是这个样子的:

我们以 A3×3×B3×2 为例。

(1)A3×3×B3×2(2)(3)=[a1,1a1,2a1,3a2,1a2,2a2,3a3,1a3,2a3,3]×[b1,1b1,2b2,1b2,2b3,1b3,2](4)(5)=[a1,1b1,1+a1,2b2,1+a1,3b3,1a2,1b1,1+a2,2b2,1+a2,3b3,1a3,1b1,1+a3,2b2,1+a3,3b3,1a1,1b1,2+a1,2b2,2+a1,3b3,2a2,1b1,2+a2,2b2,2+a2,3b3,2a3,1b1,2+a3,2b2,2+a3,3b3,2]

除了交换律以外,矩阵乘法只满足结合律和左、右分配律。

矩阵的幂运算与正常的幂运算一样,都是自乘多少次,但是由于矩阵乘法的特殊性质,我们只能给方阵求幂。

Ak=AAAAAAk of

根据这种性质,我们可以像对数字一样进行快速幂,逻辑与之前一样。

转置

转置就是将一个矩阵顺时针旋转90度。

例:

[114514]T=[151144]

我们使用 AT 来表示矩阵 A 的转置。

转置满足以下法则:

(6)(AT)T=A(7)(8)(λA)T=λAT(9)(10)(AB)T=BTAT

实现

我们可以使用一个二维数组来存储矩阵。

就像这个样子:

struct Matrix { int n, m; int a[N][N]; };

定义一个初始化函数:

Matrix() {}; Matrix(int n, int m) : n(n), m(m) { memset(a, 0, sizeof(a)) };

重载一下运算符:

加:

friend Matrix operator + (const Matrix &a, const Matrix &b) { Matrix c(a.n, a.m); for(int i = 1; i <= a.n; i++) for(int j = 1; j <= a.m; j++) c.a[i][j] = a.a[i][j] + b.a[i][j]; return c; }

乘:

friend Matrix operator * (const Matrix &a, const 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.a[i][j] += a.a[i][k] * b.a[k][j]; return c; }

乘方(快速幂):

friend Matrix operator ^ (Matrix &a, int n) { Matrix c(a.n, a.n); for(int i = 1; i <= a.n; i++)c.a[i][i] = 1; while(n) { if(n & 1) c = c * a; a = a * a; n >>= 1; } return c; }

再继续加一些其他的重载运算符之后就是这个样子:

struct Matrix { int n, m; int a[N][N]; Matrix() {}; Matrix(int n, int m) : n(n), m(m) { memset(a, 0, sizeof(a)) }; friend Matrix operator + (const Matrix &a, const Matrix &b) {//加 Matrix c(a.n, a.m); for(int i = 1; i <= a.n; i++) for(int j = 1; j <= a.m; j++) c.a[i][j] = a.a[i][j] + b.a[i][j]; return c; } friend Matrix operator - (const Matrix &a, const Matrix &b) {//减 Matrix c(a.n, a.m); for(int i = 1; i <= a.n; i++) for(int j = 1; j <= a.m; j++) c.a[i][j] = a.a[i][j] - b.a[i][j]; return c; } Matrix operator -() const {//取反 Matrix c(n, m); for(int i = 1; i <= n; i++) for(int j = 1; j <= m; j++) c.a[i][j] = -a[i][j]; return c; } friend Matrix operator * (const Matrix &a, const 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.a[i][j] += a.a[i][k] * b.a[k][j]; return c; } friend Matrix operator * (const Matrix &a, int k) {//数乘 Matrix c(a.n, a.m); for(int i = 1; i <= a.n; i++) for(int j = 1; j <= a.m; j++) c.a[i][j] = a.a[i][j] * k; return c; } friend Matrix operator ^ (Matrix &a, int n) {//快速幂 Matrix c(a.n, a.n); for(int i = 1; i <= a.n; i++)c.a[i][i] = 1; while(n) { if(n & 1) c = c * a; a = a * a; n >>= 1; } return c; } };

但其实更常见的是这样子:

struct Matrix { int n, m; int a[N][N]; Matrix() {}; Matrix(int n, int m) : n(n), m(m) { memset(a, 0, sizeof(a)); }; friend Matrix operator + (const Matrix &a, const Matrix &b) { Matrix c(a.n, a.m); for(int i = 1; i <= a.n; i++) for(int j = 1; j <= a.m; j++) c.a[i][j] = a.a[i][j] + b.a[i][j]; return c; } friend Matrix operator * (const Matrix &a, const 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.a[i][j] += a.a[i][k] * b.a[k][j]; return c; } friend Matrix operator ^ (Matrix &a, int n) { Matrix c(a.n, a.n); for(int i = 1; i <= a.n; i++)c.a[i][i] = 1; while(n) { if(n & 1) c = c * a; a = a * a; n >>= 1; } return c; } };

应用

矩阵加速递推

就以矩阵加速斐波那契数列递推为例吧。

我们将第 n 项斐波那契数简写为 Fn

我们知道 Fn=Fn1+Fn2,我们就考虑把斐波那契数列的相邻两项放在一个行(或者列,根据个人习惯)矩阵里面,就像这个样子:[FiFi1]

我们需要把 [FiFi1] 变成 [Fi1+fiFi],同时需要用到矩阵乘法。

因为

[ab]×[xyzw]=[ax+bzay+bw]

所以我们如果想让 [ab] 变成 [a+ba] 的话,我们就需要将其乘以 [1110]

然后就是矩阵快速幂了。
因为我们一开始的 [11][F2F1],所以我们只需要将其乘以 [1110]n2 即可得到 Fn

矩阵辅助维护信息

这种一般就是对于那种同时需要维护多种信息,还需要支持一大堆复杂的操作,但是推式子的时候不会超过一次的那种题,就比如说这一个:

THUSC 大魔法师

我们可以发现其操作(Ai=Ai+kBi=Bi×kCi=k)均未出现两个未知量相乘的情况,即可判定其可以利用矩阵乘法来维护。


__EOF__

本文作者Kaiser Wilheim
本文链接https://www.cnblogs.com/kaiserwilheim/p/16245133.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   南陽劉子驥  阅读(242)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示