一般矩阵快速幂的形式 :
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=1∑k1aif(n−i)+i=1∑k2big(n−i)+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=1∑kaif(n−i)
这就是常系数线性递推的基本形式。一般可以做到 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 (λI−A)v=0。
这个等式有非零解的充要条件是 d e t ( λ I − A ) = 0 det(\lambda I-A)=0 det(λI−A)=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(λi−x),而且带入 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(λiI−A)
考虑将 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) (λiI−A)(λjI−A)=(λjI−A)(λiI−A),这个直接展开就好了,因为矩阵满足加法交换律。证毕。
那么假设当前乘的特征向量为 v ⃗ a \vec v_a va,有:
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)va=Πi(λiI−A)∗va=Πi!=a(λiI−A)∗(λaI−A)∗va
而矩阵乘法满足结合律,对于后半部分,展开得:
( λ 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 (λaI−A)∗va=λaIva−Ava。
根据定义, λ a I v ⃗ a − A v ⃗ a = 0 \lambda_aI\vec v_a-A\vec v_a=0 λaIva−Ava=0,那么代回原式可得 0 0 0矩阵。
它同时是一个关于 A A A的 k k k次多项式。那么假设我们求 A w A^w Aw,可以做多项式取模,模掉多余的0矩阵得到一个关于A的 k − 1 k-1 k−1次多项式: 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=0∑k−1aiAi
设初始递推矩阵为 h = h k , h k − 1 , h k − 2 , . . . , h 1 h={h_k,h_{k-1},h_{k-2},...,h_1} h=hk,hk−1,hk−2,...,h1,那么答案为:
∑ a i ∗ [ ( h ∗ A i ) ] 11 \sum a_i*[(h*A^i)]_{11} ∑ai∗[(h∗Ai)]11,而 h ∗ A i h*A^i h∗Ai的第一项就是 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} ⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛a1100⋮00a2010⋮00a3001⋮00⋯⋯⋯⋯⋱⋯⋯ak−2000⋮10ak−1000⋮01ak000⋮00⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛hk−1hk−2hk−3⋮h2h1h0⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞=⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛hkhk−1hk−2⋮h3h2h1⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞
特征多项式:
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(λ)=∣λI−A∣=⎝⎜⎜⎜⎜⎜⎜⎜⎜⎜⎛λ−a1−100⋮00−a2λ−10⋮00−a30λ−1⋮00⋯⋯⋯⋯⋱⋯⋯−ak−2000⋮−10−ak−1000⋮λ−1−ak000⋮0λ⎠⎟⎟⎟⎟⎟⎟⎟⎟⎟⎞
考虑对第一行展开分别求和,有:
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=λk−a1λk−1−a2λk−2−⋯−ak
其中 A 1 i A_{1i} A1i为 A A A的代数余子式。
至此,常系数齐次线性递推也就可以优化矩阵快速幂了。
##小优化
1.是不是觉得计算出结果后很麻烦还要一项一项相乘??不用!!
直接计算 A n A^{n} An而不是 A n − k A^{n-k} An−k那么最后结果会是向量的最后一位。此时 A i ( i ≤ k ) A^{i}(i\le k) Ai(i≤k)的最后一位是原递推式的一项,可以直接求取。
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=aj∗ai−j。
附上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);
}