淺談矩陣[簡潔易懂]——上篇

我這種小蒟蒻就只能淺談一下矩陣這種神奇的東西啦。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高級篇的中和下先咕着...)

相關文章
相關標籤/搜索