矩阵加速递推

A A A n ∗ s n*s ns的矩阵 B B B s ∗ m s*m sm的矩阵,则 A ∗ B A*B AB的结果 C C C n ∗ m n*m nm的矩阵。
给出矩阵乘法的公式: C i j = ∑ k = 1 s A i k B k j C_{ij}=\sum_{k=1}^sA_{ik}B_{kj} Cij=k=1sAikBkj ,其中 i i i表示行, j j j表示列。
简单来说, C C C矩阵中的第 i i i行第 j j j列由 A A A矩阵的第 i i i行和 B B B矩阵的第 j j j列对应相乘得到。
因此,对于许多递推问题,我们可以构建转移矩阵来将递推转移到下一步的状态。

例题(洛谷P1939):

题目描述
a[1]=a[2]=a[3]=1
a[x]=a[x-3]+a[x-1] (x>3)
求a数列的第n项对1000000007(10^9+7)取余的值

输入格式
第一行一个整数T,表示询问个数。
以下T行,每行一个正整数n

输出格式
每行输出一个非负整数表示答案

说明
对于100%的数据 T<=100,n<=2*10^9

在这道题中,由于n的范围非常大,暴力递推无论是时间还是空间都不过关,这时我们就可以选择使用矩阵加速递推来完成题目

首先,题目中的递推公式为 a [ 1 ] = a [ 2 ] = a [ 3 ] = 1 , a [ x ] = a [ x − 3 ] + a [ x − 1 ] ( x > 3 ) a[1]=a[2]=a[3]=1,a[x]=a[x-3]+a[x-1] (x>3) a[1]=a[2]=a[3]=1a[x]=a[x3]+a[x1](x>3)
则可见,当 n < = 3 n<=3 n<=3时,我们不需要计算,可以直接输出(不要遗忘这一类的边界情况)

而当 n > 3 n>3 n>3时,我们就只能用递推公式来计算。当 n n n的值非常大时,暴力递推不能在时限内完成计算,因此我们考虑构建转移矩阵,使得
[ a [ n ] a [ n − 1 ] a [ n − 2 ] ] = ( 转 移 矩 阵 ) ∗ [ a [ n − 1 ] a [ n − 2 ] a [ n − 3 ] ] \left[ \begin{matrix} a[n]\\a[n-1]\\a[n-2] \end{matrix} \right]=(转移矩阵)*\left[ \begin{matrix} a[n-1]\\a[n-2]\\a[n-3] \end{matrix} \right] a[n]a[n1]a[n2]=()a[n1]a[n2]a[n3]
根据矩阵乘法的定义和已知递推公式(计算规律),我们可以得到这样的转移矩阵:
[ 1 0 1 1 0 0 0 1 0 ] \left[ \begin{matrix} 1&0&1\\1&0&0\\0&1&0\end{matrix} \right] 110001100
所以有 [ a [ n ] a [ n − 1 ] a [ n − 2 ] ] = [ 1 0 1 1 0 0 0 1 0 ] ∗ [ a [ n − 1 ] a [ n − 2 ] a [ n − 3 ] ] \left[ \begin{matrix} a[n]\\a[n-1]\\a[n-2] \end{matrix} \right]=\left[ \begin{matrix} 1&0&1\\1&0&0\\0&1&0\end{matrix} \right]*\left[ \begin{matrix} a[n-1]\\a[n-2]\\a[n-3] \end{matrix} \right] a[n]a[n1]a[n2]=110001100a[n1]a[n2]a[n3]
由于矩阵具有结合性,因此可以使用快速幂的思想加速计算,将时间复杂度从 O ( n ) O(n) O(n)降到 O ( n l o g n ) O(nlogn) O(nlogn),同时,由于矩阵快速幂的对象是方阵,我们可以将两个 1 ∗ 3 1*3 13的方阵补“0”得到 3 ∗ 3 3*3 33的方阵,这样并不影响我们所需求的计算结果,这样操作,就有: [ a [ n ] 0 0 a [ n − 1 ] 0 0 a [ n − 2 ] 0 0 ] = [ 1 0 1 1 0 0 0 1 0 ] ∗ [ a [ n − 1 ] 0 0 a [ n − 2 ] 0 0 a [ n − 3 ] 0 0 ] \left[ \begin{matrix} a[n]&0&0\\a[n-1]&0&0\\a[n-2]&0&0\end{matrix} \right]=\left[ \begin{matrix} 1&0&1\\1&0&0\\0&1&0\end{matrix} \right]*\left[ \begin{matrix} a[n-1]&0&0\\a[n-2]&0&0\\a[n-3]&0&0 \end{matrix} \right] a[n]a[n1]a[n2]000000=110001100a[n1]a[n2]a[n3]000000
热后就可以用矩阵快速幂快速求解了。

需要注意的是,求 a [ n ] a[n] a[n]时,转移矩阵只有 n − 1 n-1 n1次幂(与等比数列中 a [ n ] = a [ 1 ] ∗ q n − 1 a[n]=a[1]*q^{n-1} a[n]=a[1]qn1 n − 1 n-1 n1同理),为了方便快速幂计算,我们仍求 n n n次幂,输出时输出所需的结果即可。(也就是说输出的结果不一定总是 a n s [ 1 ] [ 1 ] ans[1][1] ans[1][1])

例题代码:

#include
#include
#define ll long long
using namespace std;
const int p=1e9+7;
ll t,n;
struct mat{
	ll a[4][4];
	mat() {memset(a,0,sizeof(a));}
	inline void build()
	{a[1][1]=a[2][2]=a[3][3]=1;}
	inline void cm()
	{a[1][1]=a[2][1]=a[3][2]=a[1][3]=1;}
};
mat operator*(const mat &a,const mat &b)
{
	mat c;
	for(int k=1;k<=3;k++)
		for(int i=1;i<=3;i++)
			for(int j=1;j<=3;j++)
				c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j]%p)%p;
	return c;
}
int main()
{
	scanf("%lld",&t);
	for(int i=1;i<=t;i++)
	{
		scanf("%lld",&n);
		if(n<=3) {printf("1\n");continue;}
		mat a;
		a.cm();
		mat ans;
		ans.build();
		while(n)
		{
			if(n&1) ans=ans*a;
			a=a*a;
			n>>=1;
		}
		printf("%lld\n",ans.a[2][1]);
	}
	return 0;
}

你可能感兴趣的:(算法)