常系数齐次线性递推优化矩阵快速幂

一般矩阵快速幂的形式 :
f ( n ) = ∑ i = 1 k 1 a i f ( n − i ) + ∑ i = 1 k 2 b i g ( n − i ) + c f(n)=\sum_{i=1}^{k_1} a_if(n-i)+\sum_{i=1}^{k_2}b_ig(n-i)+c f(n)=i=1k1aif(ni)+i=1k2big(ni)+c
可以做到 k 3 log ⁡ n k^3 \log n k3logn的递推,在某些特殊转移下,有更加快速的方法。

若有如下转移
f ( n ) = ∑ i = 1 k a i f ( n − i ) f(n)=\sum_{i=1}^k a_if(n-i) f(n)=i=1kaif(ni)
这就是常系数线性递推的基本形式。一般可以做到 k log ⁡ k log ⁡ n k\log k \log n klogklogn,比较简单的写法也可以做到 k 2 log ⁡ n k^2 \log n k2logn,下面介绍如何优化。

##特征多项式
前置技能:矩阵的秩

特征值,特征向量:
若有常数 λ \lambda λ,向量 v ⃗ \vec v v ,满足 λ v ⃗ = A v ⃗ \lambda\vec v=A\vec v λv =Av ,则称向量 v ⃗ \vec v v 为矩阵 A A A的一组特征向量,$\lambda 为 矩 阵 为矩阵 A$的一组特征值。
秩为k的矩阵有k组线性不相关的特征向量。

特征多项式
对关系式进行变换: ( λ I − A ) v ⃗ = 0 (\lambda I-A)\vec v=0 (λIA)v =0
这个等式有非零解的充要条件是 d e t ( λ I − A ) = 0 det(\lambda I-A)=0 det(λIA)=0,即为矩阵不满秩。
可以得到一个 k k k次多项式,这个多项式的值等于0时把这个方程称为矩阵 A A A特征方程。这个多项式称为矩阵 A A A特征多项式
k个解自然就是 k k k个特征值辣(并不需要解出来,怎么处理后面会说)!

跟据算数基本定理,最终的多项式有k个解,可以写作 f ( x ) = Π i ( λ i − x ) f(x)=\Pi_i(\lambda_i-x) f(x)=Πi(λix),而且带入 A A A矩阵本身会得到 0 0 0矩阵(Cayley-Hamilton 定理)。

证明:
(下面的证明是博主以前学OI的时候写的一种比较复杂的证明,其实可以直接用伴随矩阵来证明)
f ( A ) = Π i ( λ i I − A ) f(A)=\Pi_i(\lambda_i I -A) f(A)=Πi(λiIA)
考虑将 f ( A ) f(A) f(A)得到的矩阵分别乘特征向量,如果最后都得到了0矩阵,因为这几个特征向量线性不相关,那么可证明 f ( A ) f(A) f(A)乘以任意向量都会得到 0 0 0矩阵,从而 A A A 0 0 0矩阵。

现在问题转化为证明 f ( A ) f(A) f(A)乘任意特征向量值得到 0 0 0矩阵。首先证明 ( λ i I − A ) ( λ j I − A ) = ( λ j I − A ) ( λ i I − A ) (\lambda_i I-A)(\lambda_j I - A)=(\lambda_j I - A)(\lambda_i I-A) (λiIA)(λjIA)=(λjIA)(λiIA),这个直接展开就好了,因为矩阵满足加法交换律。证毕。

那么假设当前乘的特征向量为 v ⃗ a \vec v_a v a,有:
f ( A ) v ⃗ a = Π i ( λ i I − A ) ∗ v ⃗ a = Π i ! = a ( λ i I − A ) ∗ ( λ a I − A ) ∗ v ⃗ a \begin{aligned}f(A)\vec v_a&=\Pi_{i}(\lambda_iI-A)*\vec v_a\\&=\Pi_{i!=a}(\lambda_iI-A)*(\lambda_aI-A)*\vec v_a &&\end{aligned} f(A)v a=Πi(λiIA)v a=Πi!=a(λiIA)(λaIA)v a

而矩阵乘法满足结合律,对于后半部分,展开得:
( λ a I − A ) ∗ v ⃗ a = λ a I v ⃗ a − A v ⃗ a (\lambda_aI -A)*\vec v_a=\lambda_aI\vec v_a-A\vec v_a (λaIA)v a=λaIv aAv a

根据定义, λ a I v ⃗ a − A v ⃗ a = 0 \lambda_aI\vec v_a-A\vec v_a=0 λaIv aAv a=0,那么代回原式可得 0 0 0矩阵。

它同时是一个关于 A A A k k k次多项式。那么假设我们求 A w A^w Aw,可以做多项式取模,模掉多余的0矩阵得到一个关于A的 k − 1 k-1 k1次多项式: g ( A ) = A w % f ( A ) = ∑ i = 0 k − 1 a i A i g(A)=A^w\% f(A) =\sum_{i=0}^{k-1}a_iA^i g(A)=Aw%f(A)=i=0k1aiAi

设初始递推矩阵为 h = h k , h k − 1 , h k − 2 , . . . , h 1 h={h_k,h_{k-1},h_{k-2},...,h_1} h=hk,hk1,hk2,...,h1,那么答案为:
∑ a i ∗ [ ( h ∗ A i ) ] 11 \sum a_i*[(h*A^i)]_{11} ai[(hAi)]11,而 h ∗ A i h*A^i hAi的第一项就是 h i + 1 h_{i+1} hi+1,那么可以直接 O ( n ) O(n) O(n)计算了。因为多项式取模可以用快速傅里叶变换做到 k log ⁡ k k\log k klogk,加上快速幂的时间复杂度,总复杂度为 O ( k log ⁡ k log ⁡ n ) O(k\log k\log n) O(klogklogn)。为了方便,可以将多项式取模写 k 2 k^2 k2算法,那么总复杂度是 O ( k 2 log ⁡ n ) O(k^2\log n) O(k2logn)的。

##求解特征多项式
要做多项式取模的前提是要知道多项式。显然解出 k k k个特征值是不现实的,考虑到常系数齐次递推的特点,可以快速求出矩阵的特征多项式:
首先,列出转移方程:

( a 1 a 2 a 3 ⋯ a k − 2 a k − 1 a k 1 0 0 ⋯ 0 0 0 0 1 0 ⋯ 0 0 0 0 0 1 ⋯ 0 0 0 ⋮ ⋮ ⋮ ⋱ ⋮ ⋮ ⋮ 0 0 0 ⋯ 1 0 0 0 0 0 ⋯ 0 1 0 ) ( h k − 1 h k − 2 h k − 3 ⋮ h 2 h 1 h 0 ) = ( h k h k − 1 h k − 2 ⋮ h 3 h 2 h 1 ) \begin{pmatrix} a_1 & a_2 & a_3 & \cdots & a_{k - 2} & a_{k - 1} & a_k \\ 1 & 0 & 0 & \cdots & 0 & 0 & 0 \\ 0 & 1 & 0 & \cdots & 0 & 0 & 0 \\ 0 & 0 & 1 & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & 1 & 0 & 0 \\ 0 & 0& 0 & \cdots & 0 & 1 & 0 \end{pmatrix} \begin{pmatrix}h_{k - 1} \\ h_{k - 2} \\ h_{k - 3} \\ \vdots \\ h_2 \\ h_1 \\ h_0 \end{pmatrix} = \begin{pmatrix}h_{k} \\ h_{k - 1} \\ h_{k - 2} \\ \vdots \\ h_3 \\ h_2 \\ h_1 \end{pmatrix} a110000a201000a300100ak200010ak100001ak00000hk1hk2hk3h2h1h0=hkhk1hk2h3h2h1

特征多项式:
f ( λ ) = ∣ λ I − A ∣ = ( λ − a 1 − a 2 − a 3 ⋯ − a k − 2 − a k − 1 − a k − 1 λ 0 ⋯ 0 0 0 0 − 1 λ ⋯ 0 0 0 0 0 − 1 ⋯ 0 0 0 ⋮ ⋮ ⋮ ⋱ ⋮ ⋮ ⋮ 0 0 0 ⋯ − 1 λ 0 0 0 0 ⋯ 0 − 1 λ ) f(\lambda) = |\lambda I - A| = \begin{pmatrix} \lambda - a_1 & -a_2 & -a_3 & \cdots & -a_{k - 2} & -a_{k - 1} & -a_k \\ -1 & \lambda & 0 & \cdots & 0 & 0 & 0 \\ 0 & -1 & \lambda & \cdots & 0 & 0 & 0 \\ 0 & 0 & -1 & \cdots & 0 & 0 & 0 \\ \vdots & \vdots & \vdots & \ddots & \vdots & \vdots & \vdots \\ 0 & 0 & 0 & \cdots & -1 & \lambda & 0 \\ 0 & 0& 0 & \cdots & 0 & -1 & \lambda \end{pmatrix} f(λ)=λIA=λa110000a2λ1000a30λ100ak200010ak1000λ1ak0000λ

考虑对第一行展开分别求和,有:

f ( λ ) = ( λ − a 1 ) A 11 + ( − a 2 ) A 12 + ⋯ + ( − a k ) A 1 n = λ k − a 1 λ k − 1 − a 2 λ k − 2 − ⋯ − a k f(\lambda) = (\lambda - a_1)A_{11} + (-a_2)A_{12} + \cdots + (-a_k)A_{1n} = \lambda ^ k - a_1 \lambda ^ {k - 1} - a_2 \lambda ^ {k - 2} - \cdots - a_k f(λ)=(λa1)A11+(a2)A12++(ak)A1n=λka1λk1a2λk2ak

其中 A 1 i A_{1i} A1i A A A的代数余子式。

至此,常系数齐次线性递推也就可以优化矩阵快速幂了。

##小优化
1.是不是觉得计算出结果后很麻烦还要一项一项相乘??不用!!
直接计算 A n A^{n} An而不是 A n − k A^{n-k} Ank那么最后结果会是向量的最后一位。此时 A i ( i ≤ k ) A^{i}(i\le k) Ai(ik)的最后一位是原递推式的一项,可以直接求取。

2.注意 k 2 k^2 k2计算并取模时枚举的顺序,依次枚举 i , j i,j i,j加到 i + j i+j i+j上而不是依次枚举 i , j i,j i,j, a i = a j ∗ a i − j a_i=a_j*a_{i-j} ai=ajaij

附上BZOJ4161的代码,这是一道常系数齐次线性递推的裸题。可以用 k 2 log ⁡ n k^2\log n k2logn的方法过。

#include
using namespace std;
inline int read(){
	char ch=getchar();int i=0,f=1;
	while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
	while(isdigit(ch)){i=(i<<1)+(i<<3)+ch-'0';ch=getchar();}
	return i*f;
}
const int Maxn=2e3+20,Mod=1000000007;
#define rg register 
int n,k,a[Maxn<<1],b[Maxn<<1],c[Maxn<<1],h[Maxn],tp[Maxn<<1],ans,lim;
inline void add(int &x,int y){
	x+=y;(x>Mod?x-=Mod:(x<0?x+=Mod:0));
}
inline void mul(int *A,int *B){
	for(rg int i=0;i<=lim;i++)tp[i]=0;
	for(rg int i=0;i<=k;i++)
		for(rg int j=0;j<=k;j++)
			tp[i+j]=(tp[i+j]+1ll*A[i]*B[j])%Mod;
	for(rg int i=lim;i>=k;i--){
		for(rg int j=0;j<k;j++)
			tp[i-k+j]=(tp[i-k+j]+1ll*tp[i]*a[k-j])%Mod;
		tp[i]=0;
	}
	for(int i=0;i<=lim;i++)A[i]=tp[i];
}
inline void power(int *A,int B,int *C){
	for(;B;B>>=1,mul(A,A))if(B&1)mul(C,A);
}
int main(){
	n=read(),k=read();lim=k<<1;
	for(int i=1;i<=k;i++)a[i]=read();
	for(int i=1;i<=k;i++)h[i]=(read()%Mod+Mod)%Mod;
	if(n<k){printf("%d\n",h[n+1]);return 0;}
	b[0]=1;c[1]=1;power(c,n,b);
	for(int i=0;i<k;i++)add(ans,1ll*b[i]*h[i+1]%Mod);
	printf("%d\n",ans);
}

你可能感兴趣的:(常系数齐次线性递推)