简介
什么是常系数齐次线性递推?
设有数列
{a0,a1,a2⋯}满足递推关系
an=i=1∑kan−i∗fi,则称该数列满足k阶齐次线性递推关系。
矩阵乘法
这个很普及的东西还是提一下吧。
假设我们有一个满足k阶齐次线性递推关系的数列
a0,a1,a2⋯,它满足的齐次线性递推关系为
an=i=1∑kan−i∗fi,现在我们要求
an。
假设我们现在维护着一个列矩阵,它的行数为k。
A=anan−1⋯an−k+2an−k+1
我们考虑如何让这个矩阵的所有元素都向前递推一格,不难设计出k行k列的转移矩阵。
M=f110⋯0f201⋯0f300⋯0f400⋯0⋯⋯⋯⋯⋯fk−200⋯1fk−100⋯0
我们可以得到
f110⋯0f201⋯0f300⋯0f400⋯0⋯⋯⋯⋯⋯fk−200⋯1fk−100⋯0×an−1an−2⋯an−k+1an−k=anan−1⋯an−k+2an−k+1
现在我们要求
an,根据矩阵乘法的结合律,易得到:
f110⋯0f201⋯0f300⋯0f400⋯0⋯⋯⋯⋯⋯fk−200⋯1fk−100⋯0n×ak−1ak−2⋯a1a0=an+k−1an+k−2⋯an+1an
所以我们只需要算出
Mn×A,然后取最后一个数就可以了。
可以使用矩阵快速幂,复杂度
O(k3log2n)
特征多项式
特征值,特征向量:
若有常数
λ,向量
v,满足
λv=Av ,则称向量
v为矩阵
A的一组特征向量,
λ为矩阵
A的一组特征值。
秩为k的矩阵有k组线性不相关的特征向量。
特征多项式
对关系式进行变换:
(λI−A)v=0。
这个等式有解的充要条件是
det(λI−A)=0,大致可以看做向量被拍扁之类的。
可以得到一个
k次多项式,这个多项式的值等于
0时把这个方程称为矩阵
A的
特征方程。这个多项式称为矩阵
A的
特征多项式。
特征多项式记为
f(x)=det(λI−A)。
其中,
det()为行列式函数,
I为单位矩阵。
k个解自然就是
k个特征值。(并不需要解出来,怎么处理后面会说)
跟据算数基本定理,最终的多项式有
k个解,可以写作
f(x)=∏i(λi−x)。
Cayley-Hamilton定理
根据Cayley-Hamilton定理,可知
f(A)=O,
O为0矩阵。
下面给出一个简单证明:
f(A)=i∏(λiI−A)
考虑将
f(A)得到的矩阵分别乘特征向量,如果最后都得到了
0矩阵,因为这几个特征向量线性不相关,那么可证明
f(A)乘以任意向量都会得到
0矩阵,从而
f(A)为
0矩阵。
现在问题转换为证明
f(A)乘任意特征向量都会得到
0矩阵。
先证明:
(λiI−A)×(λjI−A)=(λjI−A)×(λiI−A)
展开即可,不再赘述。
现在考虑任意一个特征向量
vi ,
f(A)×vi=j!=i∏×(λjI−A)×(λiI−A)×vi由特征向量和特征值的定义可知,(λiI−A)×vi=0∴f(A)×vi=O
证毕。
优化递推
根据矩阵快速幂那套理论,我们只要求出来
Mn就可以了。
我们考虑
M的特征多项式
f(x),这是一个
k次多项式。
我们将
M带入,就会得到一个关于
M的
k次多项式。
我们可以将
Mn分解为
Mn=f(M)×g(M)+R(M)。
由于
f(M)=O,所以
Mn=R(M),
R(M)是一个次数不超过
k−1的多项式。
也就是说,我们只要求出来
Mn%f(M) 就万事大吉了。
但是我们怎么求呢?
我们考虑快速幂的过程(就是倍增)。
假设我们现在已知
g(M)=M2i%f(M),现在要求
h(M)=M2i+1%f(M)。
一个直接的想法就是令
H(M)=g(M)×g(M)。
但是这样做,
H(M) 的次数是
2k−2的。
那我们考虑原本的递推关系,
an=i=1∑kan−i∗fi。
不难得到
Mn=i=1∑kMn−i×fi。
所以我们可以用这个式子将多余出来的系数都向前压一位。
这样我们就得到了一个
O(k2log2n)的做法。
那有没有优化的余地呢?
我们从倍增的过程入手,可以发现
H(M)=g(M)×g(M)的过程可以由FFT加速至
O(klog2k)。
现在只要解决压系数的过程就行了。
我们只要让
h(M)=H(M)%f(M)就行了。
根据定义,
f(x)=det(xI−M),得到
f(x)=∣xI−M∣=x−a1−100⋮00−a2x−10⋮00−a30x−1⋮00⋯⋯⋯⋯⋱⋯⋯−ak−2000⋮−10−ak−1000⋮x−1−ak000⋮0x
我们对其进行第一列展开,得到
f(x)=(x−a1)M11+(−a2)M12+⋯+(−ak)M1n=xk−a1xk−1−a2xk−2−⋯−ak
Mi,j代表
M的算术余子式。
只要直接上多项式取模就行了。
最后的总复杂度
O(klog2klog2n)
我们手动模拟一下多项式取模的过程,其实可以发现我们手动向前压的过程就是在暴力取模。
例题
BZOJ4161 (要求
O(k2log2n))
洛谷P4732 (要求
O(klog2klog2n))
洛谷P3824 (要求
O(k2log2n))
洛谷P4732的
O(klog2klog2n)的代码太长了,就不粘在这里了。感兴趣的同学可以来
这里看。
附上
BZOJ4161的
O(k2log2n)代码
CPP#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cctype>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<set>
#include<map>
#include<queue>
#include<stack>
#include<cassert>
typedef long long ll;
typedef unsigned long long ull;
using namespace std;
const int P=1000000007;
const int MAXN=4010;
int n,k,ans;
int f[MAXN],h[MAXN];
struct Matrix{
int a[MAXN];
Matrix (){memset(a,0,sizeof a);}
int& operator [] (const int &i) {return a[i];}
int operator [] (const int &i) const {return a[i];}
inline Matrix operator * (const Matrix &rhs) const
{
Matrix ret;
for(int i=0;i<k;i++)
for(int j=0;j<k;j++)
(ret[i+j]+=1ll*a[i]*rhs[j]%P)%=P;
for(int i=2*k-2;i>=k;ret[i--]=0)
for(int j=1;j<=k;j++)
(ret[i-j]+=1ll*ret[i]*f[j]%P)%=P;
return ret;
}
}res;
Matrix ksm(Matrix a,int b)
{
Matrix ret;
ret[0]=1;
for(;b;a=a*a,b>>=1) if(b&1) ret=ret*a;
return ret;
}
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=k;i++) scanf("%d",&f[i]),f[i]=f[i]>0?f[i]:f[i]+P;
for(int i=0;i<k;i++) scanf("%d",&h[i]),h[i]=h[i]>0?h[i]:h[i]+P;
if(n<k) printf("%d\n",h[n]);
res[1]=1;ans=0;
res=ksm(res,n);
for(int i=0;i<k;i++) ans=(ans+1ll*res[i]*h[i]%P)%P;
printf("%d\n",ans);
}