我這種小蒟蒻就只能淺談一下矩陣這種神奇的東西啦。c++
但正由於是蒟蒻,因此講的比較好懂(大概)。算法
本篇分爲兩部分——>上:矩陣加速遞歸+下:高斯消元網絡
若是沒有你想看的那我深感抱歉...我太弱了只會講這兩個(說不定之後有補充)優化
——正片開始——spa
首先,不知道什麼是矩陣的這裏請:必應
code
咱們用矩陣優化遞推通常只須要會矩陣乘法和矩陣加法,矩陣乘法固然用得最多...blog
既然是淺談,我就簡單講講矩陣的這兩個基本運算吧。遞歸
1. 矩陣加法:
要求:兩個矩陣同型(即行列數相同)。而後咱們對應位置的元素加起來就行了...get
一個N*M的矩陣A加一個N*M的矩陣B會獲得一個N*M的矩陣。it
2. 矩陣乘法:
要求:兩個矩陣中的一個矩陣的列數等於另外一個矩陣的行數。
一個N*K的矩陣A和一個K*M的矩陣B相乘會獲得一個N*M的矩陣C。
我懶得敲Latex了,本身找去...對於它任意一個元素,值爲:
$$C_{i,j}=\sum_{k=1}^{n}a_{i,k}b_{k,j} $$
我這是爲了美觀...
而後就給段代碼吧。
struct Matrix{ int a[N][N]; Matrix(){memset(a,0,sizeof a);} int ln,lm;//長度 Matrix operator *(const Matrix&b)const{ Matrix res;res.ln=ln;res.lm=b.lm; for(int i=1;i<=ln;i++) for(int j=1;j<=b.lm;j++) for(int k=1;k<=lm;k++) res.a[i][j]=(res.a[i][j]+a[i][k]*b.a[k][j])%MOD; } };
挺簡單的吧?那麼它有什麼用呢?
用處不少。好比:加速遞推,加速遞推,……(省略10w+字)。
好啦,just a joke,只是咱們只講加速遞推。
你可能會想,通常遞推都已經O(n)了,你還要加速?
固然了,一些喪心病狂的出題人超喜歡巨型數據。
好比給你個1e9的遞推啥的,都是常常的事了。
想到遞推,我就想到今年下半年斐波那契數列。
遞推式:$\text{f(n)=f(n-1)+f(n-2)}$
咳咳,只是爲了美觀
而後咱們來構造遞推矩陣。對了,有件事忘說了。
咱們考慮使用矩陣乘法加速遞推也是有前提的:
決策點的個數少,數據範圍大,線性的遞推或類線性的遞推(之後會講)
咱們注意到,fib只有兩個決策點,因而咱們嘗試使用二維矩陣,不行就加維唄。
而後咱們就構造出來了。(告訴我怎麼構造的啊?不說錘爆你狗頭)
首先,咱們構造一個答案矩陣。(???)
看,就是這個:淦,怎麼又要用Latex...
$F(n)= \left|\begin{matrix} f(n) & f(n-1) \end{matrix} \right| $
你可能會疑惑,爲何要這麼構造?
實際上,咱們但願經過F(n)獲得F(n+1)來完成一個遞推。
咱們知道$F(n+1)= \left|\begin{matrix} f(n+1) & f(n) \end{matrix} \right| $
F(n)包含了f(n)和f(n-1)兩個信息,正是咱們獲得f(n+1)所須要的。
那麼咱們設F(n)*base=F(n+1)。因此咱們但願構造出一個base矩陣來知足它的心願。
因爲F(n)*base=F(n+1),因此咱們能夠設$base= \left|\begin{matrix} a & b \\ c & d \end{matrix} \right| $
而後咱們須要知道a,b,c,d的值。(爲何base是2*2的啊)
(ans矩陣是1*2的,base矩陣得要是2*2的,才能獲得1*2的ans矩陣)。
而後咱們先把ans和base乘起來再說:
這篇怎麼這麼多Latex,點個推薦吧,我太難了TuT
$$\left|\begin{matrix} f(n) & f(n-1) \end{matrix} \right| \times \left|\begin{matrix} a & b \\ c & d \end{matrix} \right| = \left|\begin{matrix} af(n)+cf(n-1) & bf(n)+df(n-1))\end{matrix} \right| = \left|\begin{matrix} f(n+1) & f(n) \end{matrix} \right|$$
咱們再根據遞推式能夠獲得$base= \left|\begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right| $
咱們獲得了遞推矩陣,怎麼加速呢?這時咱們就要用到強大的快速冪了。
以防有讀者不知道快速冪,我這裏簡單講解一下,快速冪就是利用二進制劃分思想加速冪運算的一個簡單算法。
正整數快速冪能夠這樣寫:
ll qpow(int x,int y){ ll tmp=x,con=1; while(y){ if(y&1)con=con*tmp%MOD; tmp=tmp*tmp%MOD; y>>=1; }return con; }
可是這裏咱們須要矩陣快速冪。
咱們簡單分析能夠獲得:$F(n)=F(2) \times base \times base \times ... \times base$,base累乘了n-2次。
爲何是從F(2)開始計算呢?
咱們知道$F(2)= \left|\begin{matrix} f(2) & f(1) \end{matrix} \right| $
還知道$F(1)= \left|\begin{matrix} f(1) & f(0) \end{matrix} \right| $,然而咱們並不能計算f(0)。
因而咱們從F(2)開始計算,矩陣乘法知足結合律,因此咱們能夠獲得最終的結果:
$F(n)= \left|\begin{matrix} f(2) & f(1) \end{matrix} \right| \times \left|\begin{matrix} 1 & 1 \\ 1 & 0 \end{matrix} \right|$
至此得解,給出代碼:
#include<bits/stdc++.h> using namespace std; long long n; const long long mod=1e9+7; struct Matrix{//封裝成結構體更方便食用 long long a[3][3]; Matrix(){memset(a,0,sizeof(a));} Matrix operator *(const Matrix &b)const{ Matrix res; for(int i=1;i<=2;i++) for(int j=1;j<=2;j++) for(int k=1;k<=2;k++) res.a[i][j]=(res.a[i][j]+a[i][k]*b.a[k][j])%mod; return res; } }ans,base; void init(){//初始化ans和base矩陣 base.a[1][1]=base.a[1][2]=base.a[2][1]=1; ans.a[1][1]=ans.a[1][2]=1; } void qpow(long long b){//矩陣快速冪 while(b){ if(b&1)ans=ans*base; base=base*base; b>>=1; } } int main(){ scanf("%lld",&n); if(n<=2){ puts("1");return 0; } init(); qpow(n-2); printf("%lld",ans.a[1][1]%mod); return 0; }
斐波那契數列算是最基礎的矩陣加速遞推的例子了,接下來咱們來看看更難一點的。
好比說,咱們有這樣一個遞推式:$f(n)=3\times f(n-1)+2\times f(n-2)+f(n-3)+3$
(我爲啥要給本身挖這麼大一個坑...),這待會兒Latex要寫死我
而後咱們一塊兒來推一下吧:
首先是答案矩陣!
仍是同樣,咱們設$F(n)=\left| \begin{matrix}f(n) & f(n-1) & f(n-2) & 3\end{matrix} \right|$
記住常數項也須要加一維,那咱們的base矩陣是什麼樣的呢?
推導過程與上面一致,因爲要寫很長的一個Latex而我又比較懶,因此我就寫到紙上了...
因而咱們獲得了base矩陣:$base= \left|\begin{matrix} a & b & c & d \\ e & f & g & h \\ i & j & k & l \\ m & n & o & p \end{matrix} \right| = \left|\begin{matrix} 3 & 1 & 0 & 0 \\ 2 & 0 & 1 & 0 \\ 1 & 0 & 0 & 0 \\ 1 & 0 & 0 & 1 \end{matrix} \right|$
而後咱們寫出初始答案矩陣,爲了不出現f(0),咱們從F(3)開始計算,獲得這樣一個行向量:
$F(3)=\left| \begin{matrix} f(3) & f(2) & f(1) & 3\end{matrix}$
因爲從F(3)開始,因此咱們算到F(n)實際上只用累成n-3次base,直接qpow(n-3)。
代碼在後面,可是在看代碼前先嚐試本身改一改上面的代碼來實現這個遞推。
給出初始項:$f_1={3}, f_2={12}, f_3={45}$,檢驗項(能夠自行檢查有沒有寫對):$f_4={165},f_7={7902},f_{10}={377175} $
若是你寫對了而且明白了遞推矩陣的推導方法,那麼你就學會初級的矩陣加速遞推了。
難道還有高級的?Bingo。
記得上面說的嗎?類線性遞推咱們也能用矩陣加速。
關於類線性的矩陣加速遞推,我會找一個時間補充,如有想看的朋友能夠推薦收藏這篇文章不按期來看看。
下篇會講矩陣&高斯消元...可是我打算先更幾篇網絡流的blog。(DP高級篇的中和下先咕着...)