矩陣乘法與矩陣加速
矩陣乘法
矩陣乘法比較簡單,就是兩個矩陣相乘得到一個新矩陣的運算.
乘法的過程就是:
第一個矩陣的每一行和第二個矩陣的每一列對應位置相乘相加,放入新矩陣.
不太顯然,矩陣乘法對於參與運算的矩陣是有限制的:
即,一個 \(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\) 是給定的數.
初始矩陣仍然是 \(:\)
目標矩陣仍然是 \(:\)
那么我們要的轉移矩陣應該滿足 \(:\)
根據上面構造矩陣的方法,可以得到,轉移矩陣長這個樣子 \(:\)
完結撒花 \(!\)
你說為什么這幾種擴展的框架一模一樣?因為我復制的啊...(懶
關於矩乘更有意思的應用,我還有另一篇博文矩陣乘法與鄰接矩陣