矩阵乘法
矩阵乘法与矩阵加速
矩阵乘法
矩阵乘法比较简单,就是两个矩阵相乘得到一个新矩阵的运算.
乘法的过程就是:
第一个矩阵的每一行和第二个矩阵的每一列对应位置相乘相加,放入新矩阵.
不太显然,矩阵乘法对于参与运算的矩阵是有限制的:
即,一个 \(n\times m\) 的矩阵和一个 \(m\times k\) 的矩阵相乘得到一个 \(n \times k\) 的矩阵.
必须是形如上面那样的矩阵才能进行乘法.
从这里可以看出,一般的矩阵乘法是不满足交换律的.(个别情况除外)
显然,一次矩阵乘法的复杂度是 \(\Theta(n\times m\times k)\) 的.
两个矩阵相乘的代码如下:
struct Matrix {
LL e[N][M] , line , row ;
inline void clear () { line = row = 0 ; MEM ( e , 0 ) ; return ; }
friend Matrix operator * (Matrix a , Matrix b) {
Matrix c ; c.clear () ; c.line = a.line ; c.row = b.row ;
rep ( k , 0 , a.row - 1 ) rep ( i , 0 , a.line - 1 ) rep ( j , 0 , b.row - 1 )
c.e[i][j] = ( c.e[i][j] + a.e[i][k] * b.e[k][j] % mod ) % mod ;
return c ;
}
} ;
上面的代码采用了结构体的封装形式,用起来更方便.
矩阵快速幂
有了矩阵乘法,我们再来考虑和乘法密切相关的一种运算:幂运算.
即一个矩阵的若干次幂如何计算,显然,只有正方形的矩阵可以计算高次幂.
那么类比于实数幂次,一个矩阵的 \(p\) 次幂只需要把它自乘 \(p\) 次即可.
讨论到了幂运算的问题,就不得不提快速幂.
众所周知,快速幂的复杂度是 \(\Theta(log_2{P})\) 的,其中 \(P\) 是指数.
这样的复杂度实在令人眼馋,相比于自乘若干次快了不知道多少倍.
那矩阵的幂次也可以像快速幂那样分治处理吗?
我们知道矩阵乘法是不满足交换律的,那么它满足结合律吗?
如果它满足结合律,那么它就可以像快速幂那样运算.
事实上,矩阵乘法是满足结合律的.
于是矩阵快速幂的代码也出炉了:
inline Matrix quick (Matrix a , LL p) {
Matrix c ; c.clear () ; c.line = a.line ; c.row = a.row ;
rep ( i , 0 , c.line - 1 ) c.e[i][i] = 1 ;
while ( p ) {
if ( p & 1 ) c = c * a ;
a = a * a ; p >>= 1 ;
}
return c ;
}
十分简单!
矩阵加速
注意,由于方法的不同,同一道题目的转移矩阵的形式是不尽相同的
根据一些资料,我们知道矩乘的本质是高维向量卷积,方程代换和线性变换.
由此得到启发,能否用矩阵来转移递推方程? 答案是肯定的.
以 \(Fibonacci\) 数列的递推为例,我们来考虑构造转移矩阵.
众所周知, \(Fibonacci\) 数列的递推式是 \(f_n=f_{n-1}+f_{n-2}\).
可以发现, \(Fibonacci\) 的递推只和某一项的前两项相关.
所以我们考虑的矩阵应该是 \(2\times 2\) 的.
我们的初始矩阵是这样的:
而目标矩阵是这样的:
所以转移矩阵长这样:
我们要的矩阵转移式就是这样的:
根据矩阵乘法的过程,可以得到:
显然,\(a=1,c=1,b=1,d=0\).
于是,转移矩阵为:
初始矩阵\(emmmm...\),不就是这个嘛
一次转移就乘一次转移矩阵,自行判断幂次即可.
矩阵加速递推的扩展
\(updated:\)
扩展情况并不多,一般只是两个递推的组合,常数项的累加以及前缀和,其实本质都是递推组合.
就都以 \(Fibonacci\) 数列为例好了.
扩展 1 带常数项
定义数列 \(F_n\) 的递推式为 \(F_n=F_{n-1}+F_{n-2}+1\) , 其中 \(F_1 = F_2 = 1\).
没有常数项的部分就是一个普通的 \(Fibonacci\) 数列.
我们令 \(g_n\) 表示常数项的递推式,显然, \(g_n=g_{n-1}\).
于是我们的初始矩阵就是 \(:\)
目标矩阵就是 \(:\)
那么我们要的转移矩阵应该满足 \(:\)
根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 \(:\)
(因为这些扩展的意义就在于构造不同的矩阵,所以只讲如何构造矩阵).
扩展 2 带未知项递推
定义数列 \(F_n\) 的递推式为 \(F_n=F_{n-1}+F_{n-2}+n\) , 其中 \(F_1=F_2=1\).
没有常数项的部分就是一个普通的 \(Fibonacci\) 数列.
令 \(g_n\) 表示未知项的递推式,显然, \(g_n=g_{n-1}+1\).
于是我们的初始矩阵就是 \(:\)
目标矩阵就是 \(:\)
那么我们要的转移矩阵应该满足 \(:\)
根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 \(:\)
扩展 3 递推式的组合
令数列 \(F_n\) 的递推式为 \(F_n=F_{n-1}+F_{n-2}+f_{n-1}+f_{n-2}\),其中,\(F_1=F_2=f_1=f_2=1\).
这叫啥?双重 \(Fibonacci\) 数列?算了,还是不乱叫了.
于是我们的初始矩阵就是 \(:\)
目标矩阵就是 \(:\)
那么我们要的转移矩阵应该满足 \(:\)
根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 \(:\)
扩展 4 前缀和
令数列 \(F_n\) 的递推式为 \(F_n=F_{n-1}+F_{n-2}\) , 求 \(\sum_{i=1}^{n}{F_i}\) , 其中 \(F_1=F_2=1\).
令 \(S_i\) 表示到 \(i\) 的前缀和.
则显然有\(:\)
于是我们的初始矩阵就是 \(:\)
目标矩阵就是 \(:\)
那么我们要的转移矩阵应该满足 \(:\)
根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 \(:\)
扩展 5 带系数
令数列 \(F_n\) 的递推式为 \(F_n = a\times F_{n-1} + b\times F_{n-2}\),其中 \(F_1,F_2\) 是给定的数.
初始矩阵仍然是 \(:\)
目标矩阵仍然是 \(:\)
那么我们要的转移矩阵应该满足 \(:\)
根据上面构造矩阵的方法,可以得到,转移矩阵长这个样子 \(:\)
完结撒花 \(!\)
你说为什么这几种扩展的框架一模一样?因为我复制的啊...(懒
矩阵乘法与邻接矩阵
矩乘结合律的证明 \(:\)
矩阵乘法能进行快速幂运算的原因就是因为它具有结合律.
引例 \(1:\) [TJOI2017]可乐
相信很多人都能想出一个 \(\Theta(t\times m)\) 的做法.(虽然我没想出来,但这只是因为我菜)
问题简化一下,如果我们没有在原地停留和自爆两个操作,那么就是问从起点出发,走 \(t\) 步的不同路径数.
这个问题怎么做呢?
不考虑 \(Dp\) .
令该图的邻接矩阵是 \(G\) , 那么我们考虑 \(G^2\) 是个什么东西.(此处的幂运算是指矩阵的幂).
我们单独考虑某一行和某一列的相关运算 \(:\) 令其为 \(G_{a,i}\) 和 \(G_{i,b}\) , 令 \(G'\) 为相乘得到的矩阵,那么会有 \(:\)
容易发现,当且仅当 \(G_{a,i}\) 和 \(G_{i,b}\) 都不为零,即 \(i\) 点可连通 \(a,b\) 两点的时候上式的该项才为 \(1\) , 否则为零.
那么所有的这些情况累加起来,就是从 \(a\) 到 \(b\) 长度为 \(2\) 的路径条数.(即走 \(2\) 步从 \(a\) 走到 \(b\) 的方案数,长度是 \(2\) 是因为经过一个中间点.)
由此,我们可以得到, \(G^2\) 得到的矩阵其实表示了任意两点间长度为 \(2\) 的路径条数.
那么 \(G^3\) 是否就表示任意两点间长度为 \(3\) 的路径条数呢?
令 \(G'=G^2\) , \(G''\) 为 \(G^3\). 那么有:
分析方法与上面相同,于是我们归纳结论如下:
令 \(G\) 表示一张图的邻接矩阵表示,那么 \(G^i\) 表示任意两点间长度为 \(i\) 的路径条数.
那么我们就解决了引例的简化问题.
那么怎么处理引例中的自爆和原地不动呢?
很简单,原地不动视为自环,自爆就额外建一个虚点,表示自爆,这里要注意的是,不需要从虚点连回原图,因为自爆之后就不能再走了.
于是我们解决了引例.
那么矩乘是否仅仅只有这一个用处呢?
引例 \(2:\) USACO07NOV Cow Relays
题目大意 \(:\) 求从 \(s\) 到 \(t\) 经过 \(k\) 条边的最短路.
这个问题乍一看很眼熟,似乎就是上一个问题在细节上做一下变换得到.
但你仔细思考会发现,最短路这个看似平凡的条件竟然不能用加法和乘法解决.
但其实这也合理,因为我们知道最短路的求法都是以类似于 \(Dp\) 的松弛操作为核心的,也就是说有一个核心运算 \(: min!\)
那么是否可以用矩阵解决这个运算呢?
考虑 \(Floyd\) 的过程,其核心代码是 \(f_{i,j}=min(f_{i,j},f_{i,k}+f_{k,j})\)
这给了我们一定启发,因为 \(Floyd\) 的过程和矩乘的过程十分相似.( \(Floyd\) 的本质是滚掉一维的三维 \(Dp\))
于是,我们大胆定义新的矩乘 \(:\)
令矩阵 \(A\) 和 矩阵 \(B\) 相乘的结果为矩阵 \(C\) .
则定义:
容易发现,这个矩乘同样具有结合律.(可以从 \(min\) 运算是和 \(+\) 运算具有同样性质的二元运算符考虑,证明与普通矩乘相同).
那么这样,我们直接应用引例 \(1\) 中的结论即可解决该题.
引例 \(3:\) 最小最大边问题
找不到题目了,国集论文没给题目来源,找不到.
最小最大边问题 \(:\) 给定一张有向图,求某两点间通过边数恰好为 \(k\) 的路径,使得最大边最小.
同样的熟悉,同样的问题.
考虑如果没有长度恰好为 \(k\) 的做法,那么就是把 \(Floyd\) 的核心代码换成 \(:\)
能否采用与上面相同的方式重定义矩乘呢?答案是肯定的.
令矩阵 \(A\) 和矩阵 \(B\) 相乘的结果为矩阵 \(C\).
则定义 \(:\)
直接套用上面的结论即可.
参考文献 \(:\) 2008年国集论文(ACM Paper):矩阵乘法在信息学中的应用--余华程