OI线性代数——矩阵
目录
本章只讨论OI中的线性代数,过于高深的线性代数的知识请参考其他教材。
矩阵与向量
矩阵是一个数学概念,对应着计算机中的二维数组。例如:
A
=
[
1
3
5
1
2
4
]
A= \begin{bmatrix} 1 & 3 & 5 \\ 1 & 2 & 4 \end{bmatrix}
A=[113254]
是一个
2
×
3
2 \times 3
2×3矩阵,由两行三列组成,通过
A
[
i
,
j
]
A[i,j]
A[i,j]来指定矩阵中第
i
i
i行第
j
j
j列的元素,例如
A
[
1
,
3
]
=
3
A[1,3]=3
A[1,3]=3。
有一类特殊的
n
×
1
n \times 1
n×1矩阵叫做向量,例如:
V
=
[
1
2
3
]
V = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}
V=⎣⎡123⎦⎤
是一个拥有
3
3
3个元素的矩阵,此篇文章不再介绍计算几何中的向量。
方阵是一种
n
×
n
n \times n
n×n的矩阵,例如:
B
=
[
1
3
1
2
]
B = \begin{bmatrix} 1 & 3 \\ 1 & 2 \end{bmatrix}
B=[1132]
是一个
2
×
2
2 \times 2
2×2的方阵,一般我们研究方阵的性质。
矩阵的运算
矩阵基本运算
- 矩阵加法:定义
A
+
B
A + B
- 矩阵减法:定义
A
−
B
A – B
- 矩阵数乘:定义
a
A
aA
A
A
a
a
- 矩阵转置:定义
A
T
A^T
A
[
i
,
j
]
A[i,j]
A
[
j
,
i
]
A[j,i]
矩阵乘法
定义
A
B
AB
AB,为
A
A
A矩阵
a
×
n
a \times n
a×n乘以矩阵
B
B
B矩阵
n
×
b
n \times b
n×b,结果矩阵为大小为
a
×
b
a \times b
a×b。
完整定义:
A
B
[
i
,
j
]
=
∑
k
=
1
n
A
[
i
,
k
]
×
B
[
k
,
j
]
AB[i,j] = \sum_{k=1}^n A[i,k] \times B[k,j]
AB[i,j]=k=1∑nA[i,k]×B[k,j]
矩阵乘法满足结合律,但不满足交换律。
定义单位矩阵为一个
n
×
n
n \times n
n×n的方阵,其对角元素均为
1
1
1,其他元素均为
0
0
0。一个矩阵乘以单位矩阵不会改变这个矩阵。
矩阵的幂
定义矩阵的幂为
A
k
=
A
⋅
A
…
A
A^k=A \cdot A \ldots A
Ak=A⋅A…A,即
k
k
k个
A
A
A矩阵相乘。
矩阵相乘的朴素算法需要
O
(
k
n
3
)
O(kn^3)
O(kn3)时间,经过矩阵快速幂优化之后可以达到
O
(
n
3
log
k
)
O(n^3 \log k)
O(n3logk)的时间。
同理其他快速幂,我们也采用二进制快速幂的方式进行优化。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
int n;
ll k;
ll mod = 1000000007;
struct Matrix
{
ll arr[105][105];
Matrix()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
arr[i][j] = 0;
}
}
}
Matrix operator*(Matrix &o)
{
Matrix ans;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
ans.arr[i][j] = (ans.arr[i][j] + (arr[i][k] * o.arr[k][j]) % mod) % mod;
}
}
}
return ans;
}
Matrix fpow(ll e)
{
Matrix temp = *this;
Matrix ans;
for (int i = 1; i <= n; i++)
ans.arr[i][i] = 1;
for (; e; e >>= 1, temp = temp * temp)
{
if (e & 1)
ans = ans * temp;
}
return ans;
}
};
int main()
{
scanf("%d %lld", &n, &k);
Matrix m;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
scanf("%lld", &m.arr[i][j]);
}
}
m = m.fpow(k);
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
printf("%lld ", m.arr[i][j]);
}
printf("\n");
}
return 0;
}
线性递推
一个线性递推方程是一种数列,具有一些初始值
f
(
0
)
,
f
(
1
)
,
…
,
f
(
k
−
1
)
f(0),f(1),\ldots,f(k-1)
f(0),f(1),…,f(k−1)和一个递推式:
f
(
n
)
=
c
1
f
(
n
−
1
)
+
c
2
f
(
n
−
2
)
+
…
+
c
k
f
(
n
−
k
)
f(n) = c_1f(n-1) + c_2f(n-2) + \ldots + c_kf(n-k)
f(n)=c1f(n−1)+c2f(n−2)+…+ckf(n−k)
这里
c
i
c_i
ci都是常数。普通的递推法的时间复杂度是
O
(
k
n
)
O(kn)
O(kn),而使用矩阵快速幂递推法的时间是
O
(
k
3
log
n
)
O(k^3 \log n)
O(k3logn)。
斐波那契数列递推
斐波那契数列有如下递推式:
f
(
0
)
=
0
f
(
1
)
=
1
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
f(0) = 0 \\ f(1) = 1 \\ f(n) = f(n-1) + f(n-2)
f(0)=0f(1)=1f(n)=f(n−1)+f(n−2)
我们定义数列向量为:
V
k
=
[
f
(
k
)
f
(
k
−
1
)
]
V_k = \begin{bmatrix} f(k) \\ f(k-1) \end{bmatrix}
Vk=[f(k)f(k−1)]
定义递推矩阵为:
V
k
+
1
=
T
V
k
V_{k+1} = TV_k
Vk+1=TVk
使用矩阵进行线性递推的过程需要构造出
T
T
T矩阵。通过合理构造,我们有:
T
=
[
1
1
1
0
]
T = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix}
T=[1110]
故有:
V
n
=
T
n
[
1
0
]
V_n = T^n \begin{bmatrix} 1 \\ 0 \end{bmatrix}
Vn=Tn[10]
T
n
T^n
Tn可以在对数时间内计算出来。
更一般的递推
类比于上述定义,对于式子:
f
(
n
)
=
c
1
f
(
n
−
1
)
+
c
2
f
(
n
−
2
)
+
…
+
c
k
f
(
n
−
k
)
f(n) = c_1f(n-1) + c_2f(n-2) + \ldots + c_kf(n-k)
f(n)=c1f(n−1)+c2f(n−2)+…+ckf(n−k)
我们可以构造出更一般的
T
T
T矩阵为:
T
=
[
c
1
c
2
⋯
c
k
1
⋯
0
0
⋮
⋱
⋮
⋮
0
⋯
1
0
]
T = \begin{bmatrix} c_1 & c_2 & \cdots & c_k \\ 1 & \cdots & 0 & 0 \\ \vdots & \ddots & \vdots & \vdots \\ 0 & \cdots & 1 & 0 \\ \end{bmatrix}
T=⎣⎢⎢⎢⎡c11⋮0c2⋯⋱⋯⋯0⋮1ck0⋮0⎦⎥⎥⎥⎤
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
int n;
ll mod = 1000000007;
struct Matrix
{
ll arr[4][4];
Matrix()
{
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
arr[i][j] = 0;
}
}
}
Matrix operator*(const Matrix &o)
{
Matrix ans;
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= n; j++)
{
for (int k = 1; k <= n; k++)
{
ans.arr[i][j] = (ans.arr[i][j] + (arr[i][k] * o.arr[k][j]) % mod) % mod;
}
}
}
return ans;
}
Matrix fpow(ll e)
{
Matrix temp = *this;
Matrix ans;
for (int i = 1; i <= n; i++)
ans.arr[i][i] = 1;
for (; e; e >>= 1, temp = temp * temp)
{
if (e & 1)
ans = ans * temp;
}
return ans;
}
};
struct Query
{
ll k;
ll id;
bool operator<(const Query &o) const
{
return k < o.k;
}
} ques[120];
ll ANS[120];
int main()
{
n = 3;
int T;
scanf("%d", &T);
for (int i = 0; i < T; i++)
{
scanf("%lld", &ques[i].k);
ques[i].id = i;
}
sort(ques, ques + T);
Matrix curr;
ll currE = 0;
curr.arr[1][1] = 1;
curr.arr[2][2] = 1;
curr.arr[3][3] = 1;
for (int i = 0; i < T; i++)
{
ll k = ques[i].k;
if (k <= 3)
{
ANS[ques[i].id] = 1;
continue;
}
Matrix m;
m.arr[1][1] = 1;
m.arr[1][3] = 1;
m.arr[2][1] = 1;
m.arr[3][2] = 1;
curr = curr * m.fpow(k - 3 - currE);
currE = k - 3;
ANS[ques[i].id] = (curr.arr[1][1] + (curr.arr[1][2] + curr.arr[1][3]) % mod) % mod;
}
for (int i = 0; i < T; i++)
{
printf("%lld\n", ANS[i]);
}
return 0;
}
高斯消元与线性方程、行列式与矩阵的逆
行列式与矩阵的逆
首先我们定义矩阵伴随因子也称余子式我为:
C
[
i
,
j
]
=
(
−
1
)
i
+
j
det
(
M
[
i
,
j
]
)
C[i,j] = (-1)^{i+j}\det(M[i,j])
C[i,j]=(−1)i+jdet(M[i,j])
其中
M
[
i
,
j
]
M[i,j]
M[i,j]是原矩阵
A
A
A去掉
i
i
i行和
j
j
j列后剩下的矩阵。
则行列式的定义(按照第一行展开)为:
det
(
A
)
=
∑
j
=
1
n
A
[
1
,
j
]
C
[
1
,
j
]
\det(A) = \sum_{j = 1}^n A[1,j] C[1,j]
det(A)=j=1∑nA[1,j]C[1,j]
还可以按照某一行某一列展开均可。
我们定义矩阵
A
A
A的逆矩阵为:
A
A
−
1
=
I
AA^{-1} = I
AA−1=I
并且
A
−
1
[
i
,
j
]
=
C
[
j
,
i
]
det
A
A^{-1}[i,j] = \frac{C[j,i]}{\det A}
A−1[i,j]=detAC[j,i]
一个方阵有逆矩阵当且仅当这个矩阵
det
A
≠
0
\det A \neq 0
detA=0。
高斯消元与线性方程组
每一个线性方程组都可以写成是一个矩阵方程
A
x
⃗
=
b
⃗
A\vec{x}=\vec{b}
Ax=b,其中
A
A
A称为系数矩阵,
x
⃗
\vec{x}
x为解向量,
b
⃗
\vec{b}
b为常数向量。
高斯消元法理论的核心主要如下:
- 两方程互换,解不变;
- 方程乘以非零数
k
k
- 方程乘以数
k
k
高斯消元法在将增广矩阵化为最简形后对于自由未知量的赋值,需要掌握线性相关知识,且赋值存在人工经验的因素,使得在学习过程中有一定的困难,将高斯消元法划分为五步骤,从而提出五步骤法,内容如下:
- 增广矩阵行初等行变换为行最简形;
- 还原线性方程组;
- 求解第一个变量;
- 补充自由未知量;
- 列表示方程组通解。
#include <bits/stdc++.h>
using namespace std;
#define FR freopen("in.txt", "r", stdin)
#define FW freopen("out.txt", "w", stdout)
typedef long long ll;
double matrix[105][105];
double esp = 1e-3;
int main()
{
int n;
scanf("%d", &n);
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= n; j++)
{
scanf("%lf", &matrix[i][j]);
}
}
for (int r = 0; r < n; r++)
{
int domain = r;
// 选择系数最大的那行作为主元,为了减少精度损失
for (int j = r; j < n; j++)
{
if (abs(matrix[j][r]) > abs(matrix[domain][r]))
domain = j;
}
// 没有主元,跳过
if (abs(matrix[domain][r]) < esp)
{
continue;
}
// 交换两行
for (int j = 0; j <= n; j++)
{
swap(matrix[domain][j], matrix[r][j]);
}
// 消元
for (int l = 0; l < n; l++)
{
if (l != r)
for (int j = n; j >= r; j--)
{
matrix[l][j] -= matrix[r][j] / matrix[r][r] * matrix[l][r];
}
}
}
// 检查解的存在性
for (int r = 0; r < n; r++)
{
// 寻找这一行的主元
int ptr = r;
while (abs(matrix[r][ptr]) < esp && ptr <= n)
{
ptr++;
}
// 如果主元在常数列上,那么无解
if (ptr == n)
{
printf("-1");
return 0;
}
}
// 如果存在缺少主元的情况,那么有无穷多解
for (int r = 0; r < n; r++)
{
if (abs(matrix[r][r]) < esp)
{
printf("0");
return 0;
}
}
// 否则有唯一解
for (int r = 0; r < n; r++)
{
// 除以主元
printf("x%d=%.2lf\n", r + 1, matrix[r][n] / matrix[r][r]);
}
return 0;
}
通过求解矩阵的逆可以进行解线性方程组
x
⃗
=
A
−
1
b
⃗
\vec{x}=A^{-1}\vec{b}
x=A−1b。
具体应用
- 计算几何:通过向量、叉乘等运算去计算几何之间的关系
- 概率论:马尔科夫链、计数、矩阵快速幂优化等
- 多项式:多项式算法的基础