矩陣快速冪最佳化

all_for_god發表於2024-12-06
  • 矩陣快速冪是一種常用於DP的演算法。透過矩陣乘法去快速轉移狀態求解。
    但是由於矩陣運算的複雜度極高,因此一般而言矩陣快速冪最佳化DP的決策點不能太多。

  • 矩陣快速冪的另一種應用是圖論中的路徑經過方案數的問題
    具體來講有一個結論:鄰接矩陣的k次方表示某兩個點之間路徑距離為k的方案數,只不過需要注意在有向圖中這種路徑是有方向的。
    當然,嚴格來講這種矩陣快速冪仍然屬於最佳化DP的範疇,因為其本質是對於floyd的擴充套件(當然擴充套件後也就和DP本身沒什麼關係了)

  • 接下來會具體分上面兩種題型來總結矩陣快速冪相關的應用。

當然,在做矩陣快速冪最佳化前,先需要確保熟練掌握了矩陣快速冪本身P3390 【模板】矩陣快速冪

最佳化DP

P1962 斐波那契數列

  • 斐波那契數列作為最經典的矩陣快速冪的題,一直作為其入門練習題的首選。

  • 首先矩陣快速冪的兩點性質一定要把握好:可DP,決策點固定且較少。而斐波那契數列就完美符合這兩個要求。

  • 然後就要考慮怎樣去設計矩陣了。矩陣快速冪最佳化DP的主要思想是透過矩陣維護一些資訊,使得可以透過這些資訊可以推出下一個狀態。

  • 由於斐波那契數列是由上兩個狀態轉移而來的,因此設矩陣

\[{\large \begin{bmatrix} f_i & f_{i-1} \end{bmatrix}} \]

而下一個狀態就可以透過這個矩陣轉移。由於 \(f_{i+1}=f_i+f_{i-1}\),因此只需要乘上一個簡潔的矩陣就可以轉移了。
這個過程稍顯抽象,但看一下就懂了 只可意會不可言傳 。對於 \(f_{i+1}\),有轉移

\[{\large \begin{bmatrix} f_{i+1} & f_i \end{bmatrix}= \begin{bmatrix} f_i & f_{i-1} \end{bmatrix}\times \begin{bmatrix} 1 & 1\\ 1 & 0 \end{bmatrix}} \]

  • 因此我們就構造出了轉移矩陣以及具體的轉移方法。直接快速冪就行了。
    注意矩陣的碼風很重要,一定要形成自己固定的寫法。

一點行都沒壓

點選檢視程式碼
#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}\) 就算不出來了。
    所以考慮多維護一個東西。設矩陣為

\[{\large \begin{bmatrix} f_i & f_{i-1} & f_{i-2} \end{bmatrix}} \]

轉移就有

\[{\large \begin{bmatrix} f_{i+1} & f_{i} & f_{i-1} \end{bmatrix} = \begin{bmatrix} f_{i} & f_{i-1} & f_{i-2} \end{bmatrix}\times \begin{bmatrix} 1 & 1 & 0\\ 0 & 0 & 1\\ 1 & 0 & 0 \end{bmatrix} } \]

然後就與上面一模一樣了。

點選檢視程式碼
#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)

相關文章