詳解矩陣乘法
本篇隨筆詳細講解一下信息學奧林匹克競賽中矩陣乘法的相關內容。矩陣和矩陣乘法的相關內容是數學中線性代數部分的內容,歡迎有興趣的讀者再自行涉獵一些純粹的數學上的知識。本篇隨筆只針對矩陣乘法在信息學和算法競賽中的應用進行講解。
矩陣加減法的概念
所謂矩陣其實就是一個數陣,我們可以把它看作一個二維數組。
這是矩陣的概念。
為了方便后面的學習,我們將一個矩陣記作大寫字母,如\(A,B,C\)等等,而類比於二維數組,把矩陣中的每個數都用一個“坐標”來表示,記作:\(A(a,b)\)等等。
這里引用高中數學必修四中平面向量中我認為很搞笑的一句話:如果沒有運算,向量只是一個“路標”,因為有了運算,向量的力量無限。
好的,把這句話搬過來:如果沒有運算,矩陣只是個數陣,因為有了運算,矩陣的力量無限。(話說數學中無限力量的有好多東西啊!)
那么在這里隆重介紹矩陣加減法的概念:
就是把對應位置的兩個數相加減。
覺得太草率?
那來一波數學一點的定義:
對於兩個矩陣\(A,B\)進行加減運算,得到的結果矩陣\(C\)具備以下性質:
需要說一下的是,進行矩陣加減法的兩個矩陣必須尺寸相等。
你可能會說:這算什么啊,矩陣加減法還不能是大小不等的矩陣進行運算?
是的,就是不可以,這是規矩。后面的矩陣乘法的規矩還會更復雜。
矩陣乘法的概念
矩陣乘法的定義比較復雜,建議這個地方畫圖理解,千萬別懶,要不然會耽誤很長時間,而且還可能造成理解不透徹。
為了證明它的確很復雜,就先上一波純數學定義:
設\(A\)是\(n*m\)的矩陣,\(B\)是\(m*p\)的矩陣,那么\(C=A\times B\)是一個\(n\times p\)的矩陣,其中每個數滿足:
當然,我堅定不移地認為還是有人一看到這個式子就明白了矩陣乘法在干嘛。但是反正蒟蒻我數學比較菜,想了五分鍾才想出來,為了節約讀者的寶貴的5分鍾,我用一種比較通俗的解釋來定義矩陣乘法。
我的解釋就是:一行乘一列,再相加。
比如你現在要得出結果矩陣\(C\)中的\((3,5)\)這個數,你就把\(A\)矩陣的第\(3\)行和\(B\)矩陣的第\(5\)列(你會發現這一行和這一列含有相同個數),然后把這些數依次相乘,最后再把這些乘積加到一起即可。
這就是矩陣乘法的概念了。
再來一張圖加深理解:
通過剛剛對矩陣乘法的定義中,我們應該能得出矩陣乘法運算的規矩:即\(A\)矩陣的列數和\(B\)矩陣的行數必須相等。比如\(3\times 4\)和\(4\times 5\)的矩陣可以相乘,但是\(3\times 4\)和\(5\times 4\)則不可以。
所以我們發現矩陣乘法的運算律:滿足分配律和結合律,但是不滿足交換律,因為交換后的矩陣並不能保證還符合定義中的“規矩”。
矩陣乘法加速遞推
上文的講解已經完成了對矩陣乘法概念的介紹,屬於數學內容。其實,矩陣乘法是線性代數中很重要的一個模型,可以用來解決很多問題,事實上,它是和向量、線性方程組結合在一起的。有很多我們想不到的妙用。由於與信競關系不大,便不予過多講解。有興趣的讀者可以自己上網查閱相關資料。
那么接下來便開始介紹矩陣乘法在算法競賽中的應用:加速遞推。
我們用一道經典題目來認識矩陣乘法加速遞推的過程:
求斐波那契數列的第\(n\)項。
斐波那契其實是最基本的遞推問題,其遞推式基本成為常識:\(f(n)=f(n-1)+f(n-2)\)。現在我們要求第\(n\)項的斐波那契數列,按照這個遞推式,可以給出一個\(O(n)\)的解決方法。這個算法足以解決\(10^7\)以內的數據,並且支持取模。但是,假如要求的大一點,求\(10^{12}\)的數據呢?
我們頓時抓頭了:\(O(n)\)的已經很快了,再快一點...
矩乘就可以做到這一點。
我們來分析:為什么\(O(n)\)的算法還是不夠快?就是因為,我們在算\(f(n)\)的時候,必須先把\(f(n-1)\)和\(f(n-2)\)算出來。但是題目只需要我們給出第n項,並不需要我們算出\(1-n\)項,所以我們在求\(1-n-1\)這些項的時候耗費了大量沒必要的時間。
那么我們怎么才能把中間的遞推過程簡化呢?
設有矩陣\(F(n)\)是一個\(1\times 2\)的矩陣,\(F(n)=[fib_n\,\,\,\,fib_{n+1}]\)。我們希望通過\(F(n-1)\)求出\(F(n)\),那么這個\(F(n)\)矩陣的第一項就是要求的答案。因為\(F(n-1)=[fib_{n-1}\,\,\,fib_{n}]\),那么,我們只需要把這個矩陣的后一個數變成前一個數,並且把這兩個數加和變成后一個數。(因為\(fib_{n+1}=fib_{n}+fib_{n-1}\))
那么,我們考慮在這個矩陣\(F(n-1)\)上乘以一個“狀態矩陣”,實現這一目的。我們發現,這個矩陣是一個\(2\times 2\)的矩陣,是
每一次遞推,我們只需要乘以這個狀態矩陣即可,那么,只需要給出初值,即\(fib_1,fib_2\),使之構成矩陣\(F(1)\),設狀態矩陣為\(A\),我們只需要乘上\(n-1\)個\(A\),就能得出我們最終需要的\(F(n)\),也就是說:
這是個矩陣遞推式,我們發現,\(A^{n-1}\)可以用一個快速冪方便的解決(當然是矩陣快速冪啦,這個在后面的代碼部分會有詳細的介紹)。
這樣我們就實現了矩陣乘法優化遞推,這樣算法的時間復雜度是\(O(n^3logT)\)
總結
一般來講,具備這些條件的遞推問題,可以用矩乘進行加速:
首先,問題只需要我們求出最終的結果數據,而不要求中間數據。比如斐波那契數列,如果只求第\(n\)項的斐波那契,完全可以用上述的矩乘遞推解決。但是如果要求\(1-n\)每一項的斐波那契數,矩乘就無能為力了。
然后,我們可以抽象出一個長度為\(n\)的一行矩陣,所有的變化都是從這里開始的,而且這個矩陣在單位時間內只發生一次變化。這個條件其實就是在說遞推初值。
之后,這個遞推過程必須是線性的。即每一次遞推只能有兩種操作:乘一個系數或者相加,而不支持高次遞推。
最后,就是優化效果的問題,矩陣乘法只有在遞推次數很大,但是初值數量很少的時候才能起到較大的效果。
我們在使用矩乘進行加速遞推的時候,實際只需要考慮兩個問題:初值矩陣,狀態矩陣,剩下的就是矩乘函數和快速冪的板子問題了。(下面就講)
矩陣乘法和矩陣快速冪的代碼實現
矩陣乘法
矩陣乘法的代碼根據矩陣乘法的定義就可以解決,用三重循環達到\(O(n^3)\)的時間復雜度。
代碼:
int a[maxn][maxn],b[maxn][maxn],c[maxn][maxn];
void multiset(int a[][],int b[][],int n)
{
for(int i=1;i<=n;i++)
for(int k=1;k<=n;k++)
for(int j=1;j<=n;j++)
c[i][j]+=a[i][k]*b[k][j];
}
那么矩陣快速冪也很簡單啦!把普通快速冪的乘法運算都換成矩陣乘法就可以啦!
代碼:
int ret[maxn][maxn];
void qpow(int a[][],int n)
{
memset(ret,1,sizeof(ret));
while(n)
{
if(n&1)
multiset(ret,a,maxn);
multiset(a,a,maxn);
n>>=1;
}
}
以上就是矩陣乘法的大部分內容,感謝大家的推薦和收看。