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\times k\) 的)狀態矩陣:舉個例子,如果 \(F_n\) 受 \(F_{n-1},F_{n-2}\) 影響,那麼建立狀態矩陣為 \(\begin{bmatrix}F_{n-1} & F_{n}\end{bmatrix}\)。
- 設定(二維,\(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}\)
【例】斐波那契數列
斐波那契數列有兩個性質:(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;
}