矩阵乘法的各种总结
矩阵乘法的计算规则与代码模板
为了后面方便统一,先给出一个统一的规则(虽然有其他的版本)。
有两个矩阵相乘 A × B = C A \times B = C A×B=C 当且仅当 A A A 的列数 与 B B B 的行数相等,那么 C C C 的行数则与 A A A 的行数相等, C C C 的列数 与 B B B 的列数相等
A A A 的第一行的各个数分别于 B B B 的第一列的各个数对应相乘的和 为 C C C 的第一行第一列
A A A 的第一行的各个数分别于 B B B 的第二列的各个数对应相乘的和 为 C C C 的第一行第二列
A A A 的第二行的各个数分别于 B B B 的第一列的各个数对应相乘的和 为 C C C 的第二行第一列
⋯ \cdots ⋯
其余同理
模板如下
class Matrix {
public:
int mx[2 * N + 5][2 * N + 5];
int row, col;
Matrix (int n = 0, int m = 0, bool unit = 0) {
row = n;
col = m;
memset(mx, 0, sizeof mx);
if (unit && n == m) {
for (int i = 1; i <= n; i ++) {
mx[i][i] = 1;
}
}
}
Matrix operator * (const Matrix& other) const {
if (col != other.row) return Matrix(0, 0);
Matrix result(row, other.col);
for (int i = 1; i <= row; i ++) {
for (int j = 1; j <= other.col; j ++) {
for (int k = 1; k <= col; k ++) {
result.mx[i][j] = (result.mx[i][j] + mx[i][k] * other.mx[k][j]) % Mod;
}
}
}
return result;
}
Matrix qpow (int b) {
if (col != row) return Matrix(0, 0);
Matrix result (row, col, 1);
Matrix A = (*this);
while (b) {
if (b & 1) result = result * A;
A = A * A;
b >>= 1;
}
return result;
}
};
矩阵乘法优化 DP
对于形如 F n = a 1 × F n − 1 + a 2 × F n − 2 + ⋯ + a k × F n − k F_n = a_1 \times F_{n - 1} + a_2 \times F_{n - 2} + \cdots + a_k \times F_{n - k} Fn=a1×Fn−1+a2×Fn−2+⋯+ak×Fn−k 的 dp 方程,我们可以使用矩阵乘法进行优化。
矩阵递推的形式如下
[ 系数矩阵 ] T × [ 递推矩阵 ] = [ 答案矩阵 ] \begin{bmatrix}系数矩阵\end{bmatrix} ^ T\times \begin{bmatrix}递推矩阵\end{bmatrix}=\begin{bmatrix}答案矩阵\end{bmatrix} [系数矩阵]T×[递推矩阵]=[答案矩阵]
先忽略系数矩阵的 T T T 次方,后面会解释,我们想要知道 F n F_n Fn 显然要知道 F n − 1 , F n − 2 , ⋯ , F n − k F_{n - 1}, F_{n - 2}, \cdots, F_{n - k} Fn−1,Fn−2,⋯,Fn−k 因此递推矩阵中一定含有这几项,那么答案矩阵由递推矩阵得来显然与递推矩阵形式一致,因此我们可以填上一下两个矩阵
$$
\begin{bmatrix}&&&&&\&系&数&矩&阵\&&&&&\\end{bmatrix} \times \begin{bmatrix}F_{n - 1}\F_{n - 2}\F_{n - 3}\\cdots \F_{n - k}\end{bmatrix} =\begin{bmatrix}F_n\F_{n - 1}\F_{n - 2}\\cdots \F_{n - k + 1}\end{bmatrix}
$$
并且根据矩阵乘法规则,我们可以知道系数矩阵的大小为 k ∗ k k * k k∗k 的,现在我们要构造这个矩阵
[ A 1 , 1 A 1 , 2 ⋯ A 1 , k ⋯ ⋯ ⋯ ⋯ ] × [ F n − 1 F n − 2 F n − 3 ⋯ F n − k ] = [ F n F n − 1 F n − 2 ⋯ F n − k + 1 ] \begin{bmatrix}A_{1,1} & A_{1,2} & \cdots & A_{1,k} \\\cdots & \cdots & \cdots & \cdots\end{bmatrix}\times \begin{bmatrix}F_{n - 1}\\F_{n - 2}\\F_{n - 3}\\\cdots \\F_{n - k}\end{bmatrix} =\begin{bmatrix}F_n\\F_{n - 1}\\F_{n - 2}\\\cdots \\F_{n - k + 1}\end{bmatrix} [A1,1⋯A1,2⋯⋯⋯A1,k⋯]× Fn−1Fn−2Fn−3⋯Fn−k = FnFn−1Fn−2⋯Fn−k+1
其中 A 1 , 1 × F n − 1 + A 1 , 2 × F n − 2 + ⋯ + A 1 , k × F n − k = F n A_{1,1} \times F_{n - 1} + A_{1,2} \times F_{n - 2} + \cdots + A_{1,k} \times F_{n - k} = F_n A1,1×Fn−1+A1,2×Fn−2+⋯+A1,k×Fn−k=Fn (矩阵乘法的规则)
由递推式可知 A 1 , 1 = a 1 , A 1 , 2 = a 2 , ⋯ A 1 , k = a k A_{1,1} = a_1, A_{1,2} = a_2, \cdots A_{1,k} = a_k A1,1=a1,A1,2=a2,⋯A1,k=ak
我们已经完成了系数矩阵第一行的构造, 接下来就很简单了
[ ⋯ ⋯ ⋯ ⋯ A 2 , 1 A 2 , 2 ⋯ A 2 , k ⋯ ⋯ ⋯ ⋯ ] × [ F n − 1 F n − 2 F n − 3 ⋯ F n − k ] = [ F n F n − 1 F n − 2 ⋯ F n − k + 1 ] \begin{bmatrix}\cdots & \cdots & \cdots & \cdots \\A_{2,1} & A_{2,2} & \cdots & A_{2,k} \\\cdots & \cdots & \cdots & \cdots\end{bmatrix}\times \begin{bmatrix}F_{n - 1}\\F_{n - 2}\\F_{n - 3}\\\cdots \\F_{n - k}\end{bmatrix} =\begin{bmatrix}F_n\\F_{n - 1}\\F_{n - 2}\\\cdots \\F_{n - k + 1}\end{bmatrix} ⋯A2,1⋯⋯A2,2⋯⋯⋯⋯⋯A2,k⋯ × Fn−1Fn−2Fn−3⋯Fn−k = FnFn−1Fn−2⋯Fn−k+1
因为 F n − 1 F_{n - 1} Fn−1 已经在递推矩阵中出现了因此我们直接用就好了,让 A 2 , 1 A_{2,1} A2,1 为 1 1 1 其余全部为 0 0 0 即可。
相当于 1 × F n − 1 + 0 × F n − 2 + ⋯ + 0 × F n − k = F n − 1 1 \times F_{n - 1} + 0 \times F_{n - 2} + \cdots + 0 \times F_{n - k} = F_{n - 1} 1×Fn−1+0×Fn−2+⋯+0×Fn−k=Fn−1,剩下的 k − 2 k - 2 k−2 行依次构造即可。
最后递推矩阵每乘上一个系数矩阵相当于多往后递推一位, 因此就有了前文所述的 T T T 次方。
矩阵的等比数列求和
若有一个矩阵 A A A,求 A + A 1 + A 2 + A 3 + ⋯ + A n A + A^1 + A^2 + A^3 + \cdots + A^n A+A1+A2+A3+⋯+An 的和
因为要求和,所以说和一定要在答案矩阵里,那么不妨设 S n = A + A 1 + ⋯ + A n S_n = A + A^1 + \cdots + A^n Sn=A+A1+⋯+An,则递推式相当于 S n = S n − 1 × A + A S_n = S_{n - 1} \times A + A Sn=Sn−1×A+A
构造矩阵 (过程略) 得:
[ A U n i t 1 U n i t 0 U n i t 1 ] × [ S n − 1 A ] = [ S n A ] \begin{bmatrix}A & Unit_1 \\Unit_0 & Unit_1\end{bmatrix}\times\begin{bmatrix}S_{n - 1} \\A\end{bmatrix} =\begin{bmatrix}S_n \\A\end{bmatrix} [AUnit0Unit1Unit1]×[Sn−1A]=[SnA]
其中 U n i t 1 Unit_1 Unit1 是左上至右下对角线为 1 1 1 的单位矩阵, U n i t 0 Unit_0 Unit0 是一个全零矩阵,他们的大小都是与 A A A 相同的,相当于扩大了一倍
与上同理快速幂转移。
矩阵乘法在图论中的应用
其实,当我们用邻接矩阵存图的时候相当于构造了一个 n × n n \times n n×n的矩阵,那么这个矩阵自乘一次有什么意义呢?
我们来看一下 F l o y d 算法 Floyd 算法 Floyd算法:
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
dis[i][j] = min(dis[i][j], dis[i][k] + dis[k][j]);
}
}
}
发现它和矩阵乘法很像,其实如果我们把这个更新的结果放在一个新的数组而不是 d i s dis dis 那么我们会得到一个 类 F l o y d 算法 类 Floyd算法 类Floyd算法 可以通过矩阵快速幂实现 O ( l o g N ) O(logN) O(logN) 的得到 从 i i i 到 j j j 恰好经过 N N N 条边的最短路
现在我们在改一下 d i s dis dis 的定义:
d i s i , j 表示从 i 到 j 的方案数 dis_{i,j} 表示从 i 到 j 的方案数 disi,j表示从i到j的方案数
那么 F l o y d 算法 Floyd 算法 Floyd算法就应该写成这样
for (int k = 1; k <= n; k ++) {
for (int i = 1; i <= n; i ++) {
for (int j = 1; j <= n; j ++) {
dis[i][j] = dis[i][j] + dis[i][k] * dis[k][j];
}
}
}
标准的矩阵乘法形式
那么 类 F l o y d 算法 类 Floyd 算法 类Floyd算法 同理可以维护:从 i i i 到 j j j 恰好经过 N N N 条边的方案数
那么,如果我们相求从 i i i 到 j j j 恰好经过 ≤ N \leq N ≤N 条边的方案数
你应该已经想到了,这不就是答案矩阵 A A A 的 A + A 1 + A 2 + A 3 + ⋯ + A N A + A^1 + A^2 + A^3 + \cdots + A^N A+A1+A2+A3+⋯+AN 吗?
上面的做法时空是 O ( 4 N ) O(4N) O(4N) 的, 其实还有一种更好的方法
[ d i s 1 , 1 d i s 1 , 2 d i s 1 , 3 ⋯ d i s 1 , n 0 d i s 2 , 1 d i s 2 , 2 d i s 2 , 3 ⋯ d i s 2 , n 0 ⋯ ⋯ ⋯ ⋯ ⋯ 0 d i s n , 1 d i s n , 2 d i s n , 3 ⋯ d i s n , n 0 1 0 0 ⋯ 0 1 ] \begin{bmatrix}dis_{1,1} & dis_{1,2} & dis_{1,3} & \cdots & dis_{1, n} & 0 \\dis_{2,1} & dis_{2,2} & dis_{2,3} & \cdots & dis_{2, n} & 0 \\\cdots & \cdots & \cdots & \cdots & \cdots & 0 \\dis_{n,1} & dis_{n,2} & dis_{n,3} & \cdots & dis_{n, n} & 0 \\1 & 0 & 0 & \cdots & 0 & 1\end{bmatrix} dis1,1dis2,1⋯disn,11dis1,2dis2,2⋯disn,20dis1,3dis2,3⋯disn,30⋯⋯⋯⋯⋯dis1,ndis2,n⋯disn,n000001
多添一行一列 0 0 0 在矩阵的最后,然后将起始点的对应列的最后一个修改为 1 1 1 如图则表示从 1 1 1 号点出发,矩阵的右下角设为 1 1 1
这个 1 1 1 是可以修改的。同理让矩阵自乘 N N N次即可。
最后的答案就在终点的对应列的最后一行