-
矩陣快速冪是一種常用於DP的演算法。透過矩陣乘法去快速轉移狀態求解。
但是由於矩陣運算的複雜度極高,因此一般而言矩陣快速冪最佳化DP的決策點不能太多。 -
矩陣快速冪的另一種應用是圖論中的路徑經過方案數的問題
具體來講有一個結論:鄰接矩陣的k次方表示某兩個點之間路徑距離為k的方案數,只不過需要注意在有向圖中這種路徑是有方向的。
當然,嚴格來講這種矩陣快速冪仍然屬於最佳化DP的範疇,因為其本質是對於floyd的擴充套件(當然擴充套件後也就和DP本身沒什麼關係了) -
接下來會具體分上面兩種題型來總結矩陣快速冪相關的應用。
當然,在做矩陣快速冪最佳化前,先需要確保熟練掌握了矩陣快速冪本身P3390 【模板】矩陣快速冪
最佳化DP
P1962 斐波那契數列
-
斐波那契數列作為最經典的矩陣快速冪的題,一直作為其入門練習題的首選。
-
首先矩陣快速冪的兩點性質一定要把握好:可DP,決策點固定且較少。而斐波那契數列就完美符合這兩個要求。
-
然後就要考慮怎樣去設計矩陣了。矩陣快速冪最佳化DP的主要思想是透過矩陣維護一些資訊,使得可以透過這些資訊可以推出下一個狀態。
-
由於斐波那契數列是由上兩個狀態轉移而來的,因此設矩陣
而下一個狀態就可以透過這個矩陣轉移。由於 \(f_{i+1}=f_i+f_{i-1}\),因此只需要乘上一個簡潔的矩陣就可以轉移了。
這個過程稍顯抽象,但看一下就懂了 只可意會不可言傳 。對於 \(f_{i+1}\),有轉移
- 因此我們就構造出了轉移矩陣以及具體的轉移方法。直接快速冪就行了。
注意矩陣的碼風很重要,一定要形成自己固定的寫法。
一點行都沒壓
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ll;
#define int ll
const ll p=1e9+7;
ll n;
struct ju
{
int a[3][3];
ju(){memset(a,0,sizeof(a));}
}base,f;
ju operator * (const ju &x,const ju &y)
{
ju tmp;
for(int i=1;i<=2;i++)
for(int j=1;j<=2;j++)
for(int h=1;h<=2;h++)
tmp.a[i][j]=(tmp.a[i][j]+x.a[i][h]*y.a[h][j]%p)%p;
return tmp;
}
ju ksm(ju x,int k)
{
ju res;
for(int i=1;i<=2;i++) res.a[i][i]=1;
while(k)
{
if(k&1) res=res*x;
x=x*x;k>>=1;
}
return res;
}
signed main()
{
cin>>n;
if(n==1ll||n==2ll){cout<<'1';return 0;}
base.a[1][1]=base.a[1][2]=base.a[2][1]=1,base.a[2][2]=0;
ju ans=ksm(base,n-2);
f.a[1][1]=f.a[1][2]=1;
f=f*ans;
cout<<f.a[1][1]<<'\n';
return 0;
}
P1939 矩陣加速(數列)
-
此題本質上與上一道題是一樣的,但是轉移的位置變化了,遞推式變為了 \(f_i=f_{i-1}+f_{i-3}\)
然後就來考慮一下轉移的矩陣有什麼變化。因為要從前一和三個位置轉移,能不能直接維護這兩個值呢? -
事實上是不行的。自己手推一下就會發現,轉移的時候,第一次轉移確實能轉,但之後矩陣裡新的 \(f_{i-3}\) 就算不出來了。
所以考慮多維護一個東西。設矩陣為
轉移就有
然後就與上面一模一樣了。
點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=4;
const int p=1e9+7;
struct node
{
int a[N][N];
node(){memset(a,0,sizeof(a));}
}base,f;
node operator * (const node &x,const node &y)
{
node res;
for(int i=1;i<=3;i++)
for(int j=1;j<=3;j++)
for(int h=1;h<=3;h++) res.a[i][j]=(res.a[i][j]+x.a[i][h]*y.a[h][j])%p;
return res;
}
node ksm(node x,int k)
{
node res;for(int i=1;i<=3;i++) res.a[i][i]=1ll;
while(k)
{
if(k&1) res=res*x;
x=x*x,k>>=1;
}
return res;
}
signed main()
{
ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
int T;cin>>T;
base.a[1][1]=base.a[1][2]=1;
base.a[2][3]=1;
base.a[3][1]=1;
f.a[1][1]=f.a[1][2]=f.a[1][3]=1;
while(T--)
{
int n;cin>>n;
if(n<=3) {cout<<"1\n";continue;}
node ans=ksm(base,n-3);
ans=f*ans;
cout<<ans.a[1][1]<<'\n';
}
return 0;
}
## [P3216 [HNOI2011] 數學作業](https://www.luogu.com.cn/problem/P3216)