学习NOTE 12——矩阵

其实这个东西我一直想写,结果不知不觉间就拖了一个月了,必须得写了,再不写就忘了。

是什么

一句话:矩阵就是"按长方形排列的一堆数"。

矩阵 时由 \(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\) 的行数不相等,那它们俩就没法相乘("无意义")。

再看看怎么乘的:

具体操作:

  1. 拿出 \(A\) 的第 \(i\) 行所有数字;
  2. 拿出 \(B\) 的第 \(j\) 列所有数字;
  3. 将它们一一对应相乘 ,然后把所有乘积加起来 的到答案 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]

注意

  1. 不满足交换律 :绝大多数情况下,\(A\times B \ne B\times A\)(甚至 \(B×A\) 可能根本乘不了)。
  2. 顺序很重要 :公式里永远是 "左矩阵的行" 去乘 "右矩阵的列",不能反过来。
  3. 数字对应要齐:行和列的元素个数必须相同,否则无法对应相乘。

代码

这里直接把矩阵加法、乘法和快速幂一起放出来。

其实矩阵是支持快速幂的,就是一个矩阵的几次方:\(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的,通常是线性递推加速 ,那就可以用矩阵快速幂优化。

图论中,跑图也是一样的,用矩阵快速幂优化。