1. 程式人生 > 其它 >uniapp-網易雲-文件結構分析-圖示

uniapp-網易雲-文件結構分析-圖示

矩陣乘法是一種最為常見的矩陣運算,一個 \(n\times m\) 的矩陣和一個 \(m\times p\) 的矩陣可以進行矩陣乘法;矩陣乘法滿足結合律(\(A\times B\times C=(A\times B)\times C=A\times (B\times C)\)),但不滿足交換律(\(A\times B\neq B\times A\))。矩陣可以進行冪運算,並且可以用快速冪計算。

矩陣快速冪

void mul(int a[N],int b[N][N]){
    int c[N];
    memset(c,0,sizeof(c));
    for(int i=0;i<N;i++)
        for(int j=0;j<N;,j++)
            for(int k=0;k<N;k++)
                c[i][j]+=a[i][k]*b[k][j];
    memcpy(a,c,sizeof(c));
}
void mulself(int a[N][N]){
    int c[N][N];memset(c,0,sizeof(c));
    for(int i=0;i<N;i++)
        for(int j=0;j<N;,j++)
            for(int k=0;k<N;k++)
                c[i][j]+=a[i][k]*a[k][j];
    memcpy(a,c,sizeof(c));
}
int PowerMod(int a[N][N],int b){
    int ans[N]={...};
    while(b){
        if(b&1) mul(ans,a);
        b>>=1,mulself(a);
    }
    return ...;
}

矩陣加速 DP

一般步驟為:

  1. 設定(一維,\(1\times k\) 的)狀態矩陣:舉個例子,如果 \(F_n\)\(F_{n-1},F_{n-2}\) 影響,那麼建立狀態矩陣為 \(\begin{bmatrix}F_{n-1} & F_{n}\end{bmatrix}\)
  2. 設定(二維,\(k\times k\) 的)轉移矩陣:把你列出的轉移方程式用轉移矩陣中的常數表示出來;沿用剛才的例子,如果轉移方程式為 \(F_n=F_{n-1}+F_{n-2}\),那麼轉移矩陣為 \(\begin{bmatrix}0 & 1\\ 1&1\end{bmatrix}\),因為 \(\begin{bmatrix}F_{n-1} & F_{n}\end{bmatrix}\times\begin{bmatrix}0 & 1\\ 1&1\end{bmatrix}=\begin{bmatrix}F_{n} & F_{n+1}\end{bmatrix}\)
    。我們發現:如果狀態矩陣中,轉移之前的第 \(x\) 位置對轉移之後的第 \(y\) 位置有影響,那麼把轉移矩陣的 \((x,y)\) 位置填上 \(k\),其中 \(k\) 表示 \(<y>\) 要加上 \(<x>\) 的係數;最終,把轉移矩陣中剩下的位置都填 \(0\)

【例】斐波那契數列
斐波那契數列有兩個性質:(1)\(F_i = {F_{i-1}} + {F_{i-2}}\)(2)\(\sum_{i=1}^n F_i= F_{n+2}\)

\(Fib_n\)(從 1 開始標號)。
其實我們上邊剛才舉的例子,就是跟這題一樣的,下面直接上程式碼。

#include <bits/stdc++.h>
using namespace std;
const long long Mod=1e9+7;
void mul(long long a[2],long long b[2][2]){
    long long c[2];
    memset(c,0,sizeof(c));
    for(int i=0;i<2;i++) 
        c[i]=(a[0]*b[0][i]%Mod+a[1]*b[1][i]%Mod)%Mod;
    memcpy(a,c,sizeof(c));
}
void mulself(long long a[2][2]){
    long long c[2][2];memset(c,0,sizeof(c));
    for(int i=0;i<2;i++)
        for(int j=0;j<2;c[i][j]%=Mod,j++)
            for(int k=0;k<2;k++)
                c[i][j]+=a[i][k]*a[k][j]%Mod;
    memcpy(a,c,sizeof(c));
}
long long PowerMod(long long a[2][2],long long b){
    long long ans[2]={1,1};
    while(b){
        if(b%2) mul(ans,a);
        b/=2,mulself(a);
    }
    return ans[0];
}
int main()
{
    long long n;
    cin>>n;
    long long a[2][2]={{0,1},{1,1}};
    cout<<PowerMod(a,n-1);
}

【練】link

經典題:逼死強迫症

拿到這題,第一想法是狀壓,用 \(F_{i,j}\) 表示填滿 \(2\times i\) 的方格圖的方案數,對於最後一列(第 \(i\) 列),\(j=0,1,2,3,4\) 分別表示:不填,左邊填一個,右邊填一個,兩邊各填一個,豎著放一個。這樣我們就可以求出沒有 \(1\times 1\) 的磚塊時填滿的方案數為 \(F_{n,0}+F_{n,4}\)。那現在有分裂的磚啊!我們用 \(dp_i\) 表示將其中一塊 \(1\times 1\) 的磚放在第 \(i\) 列,另外一塊放在 \(1\sim i\) 列之間,恰好鋪滿 \(2\times i\) 的區域的方案數。我們發現 \(dp_{i-1}\) 已經幫 \(dp_i\) 做了很大一部分工作,現在我們只需考慮如何轉移。

我們發現當 \(i-1\) 時填上棕色那塊時,①②③三個塊塊我們是不會選擇去用另一個 \(1\times 1\) 填它的;那 \(i\) 時是否可以填呢?我們發現 \(i\) 時②塊是可以填的,填上後增加的方案數為 \(F_{i-3,0}+F_{i-3,4}\)。因此轉移方程為
\(dp_{i}=dp_{i-1}+2(F_{n,0}+F_{n,4})\)(乘2是因為翻過來又有一種情況)。多嘴一下,初始有 \(dp_1=dp_2=0,dp_3=2,G_0=1\)。我們奇妙地發現,\(F_{n,0}+F_{n,4}=fib_{n+1}\),是斐波那契數列!而根據組合數學,我們最終的答案應該是 \(\sum _{i=1}^n dp_i\times (F_{n-i,0}+F_{n-i,4})\)。程式碼1:(用的是狀壓求 \(F_0+F_4\)
link
Result: 50pts, TLE/RE.

然後我們把 \(G_i=fib_{i+1}\) 代入得到了更糟的結果(20pts+3TLE+4RE),這裡不展示程式碼了。

第二步,

\[dp_n=dp_{n-1}+2fib_{n-2}=dp_{n-2}+2fib_{n-3}+2fib_{n-2}=\cdots=2\sum_{i=1}^{n-2}fib_i=2(fib_n-1) \]\[ans=\sum _{i=1}^n dp_i\times fib_{n-i+1}=2 \sum_{i=1}^n (fib_i-1)\times fib_{n-i+1}=2\cdot(1+\sum_{i=1}^n fib_i\times fib_{n-i+1}-\sum_{i=1}^n fib_{n-i+1})=2\cdot(1-fib_{n+2}+\sum_{i=1}^n fib_i\times fib_{n-i+1}) \]

程式碼2:link

第三步,
\(G_i=\sum_{i=1}^n fib_i\times fib_{n-i+1}\),則 \(G_i=\sum_{i=1}^n fib_i\times fib_{n-i+1}=fib_n+\sum_{i=1}^{n-1} fib_i\times (fib_{n-i}+fib_{n-i+1}-fib_{n-i})=fib_n+\sum_{i=1}^{n-1} fib_i\times (fib_{n-i}+fib_{n-i-1})=fib_n+\sum_{i=1}^{n-1} fib_i\times fib_{n-i}+\sum_{i=1}^{n-2}fib_{(n-2)-i+1}=G_{n-1}+G_{n-2}+fib_n\)。真是有種撥雲見日的感覺!

有這個轉移方程式,得到矩陣的轉移式:

\[\begin{bmatrix}fib_{n-1} fib_n G_{n-1} G_n\end{bmatrix} \]\[\times \begin{bmatrix}0&1&0&1\\1&1&0&1\\0&0&0&1\\0&0&1&1\end{bmatrix} \]\[=\begin{bmatrix}fib_n fib_{n+1} G_n G_{n+1}\end{bmatrix} \]

程式碼3(Final):(複雜度 \(O(4^3\log n)\)

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int Mod=1e9+7;
void mul(int a[4],int b[4][4]){
    int c[4]; memset(c,0ll,sizeof(c));
    for(int i=0;i<4;i++)
        for(int j=0;j<4;j++)
            c[i]=(c[i]+a[j]*b[j][i]%Mod)%Mod;
    memcpy(a,c,sizeof(c));
}
void mulself(int a[4][4]){
    int c[4][4]; memset(c,0ll,sizeof(c));
    for(int i=0;i<4;i++)
        for(int j=0;j<4;j++)
            for(int k=0;k<4;k++)
                c[i][j]=(c[i][j]+a[i][k]*a[k][j]%Mod)%Mod;
    memcpy(a,c,sizeof(c));
}
void PowerMod(int b){
    int ans[4]={0,1,0,1};
    int a[4][4]={{0,1,0,1},{1,1,0,1},{0,0,0,1},{0,0,1,1}};
    while(b){
        if(b%2ll) mul(ans,a);
        b/=2ll,mulself(a);
    }
    int Ans=2ll*((ans[3]+Mod-(ans[0]+ans[1]*2ll%Mod)%Mod+1ll)%Mod)%Mod;
    //一定注意這裡的"+Mod",因為ans[3]和後面那堆都是%過Mod的,作差後不再一定為正數
	cout<<Ans<<endl;
}
void solve()
{
	int n;
	cin>>n;
	if(n<3){
		cout<<0<<endl; return;
	}
	PowerMod(n-1);
}
signed main(){
	int T; cin>>T;
	while(T--) solve();
	return 0;
}