【学习小记】常系数齐次线性递推

本文介绍了一种高效计算线性递推数列的方法,通过矩阵快速幂和特征多项式理论,避免了传统递推的高时间复杂度。文章详细讲解了特征值、特征向量、Hamilton-Cayley定理等数学概念,并提供了具体的算法实现,适用于解决大规模线性递推问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题引入

给出数列ggg,满足当n>mn>mn>m
gn=∑i=1mgn−i×aig_n=\sum\limits_{i=1}^{m}g_{n-i}\times a_ign=i=1mgni×ai
n&lt;=mn&lt;=mn<=m时,gn=cng_n=c_ngn=cn

m比较小,n特别大,快速计算gng_ngn

Newbie的解法

暴力递推计算

时间复杂度O(nm)O(nm)O(nm)

Pupil的解法

可以将转移和数列都写成m×mm\times mm×m的矩阵的形式,矩阵快速幂即可

时间复杂度O(m3log⁡n)O(m^3\log n)O(m3logn)

Master的解法

我们需要一些数学知识进行铺垫:

Part 1 矩阵的特征值与特征多项式

我们知道一个矩阵乘一个列向量仍然是一个列向量。

若对于m阶矩阵A,有常数λ\lambdaλ,非零列向量v⃗\vec vv,满足λv⃗=Av⃗\lambda\vec v=A\vec vλv=Av则称λ\lambdaλ为矩阵A的特征值,v⃗\vec vv为矩阵的特征向量

上式也可以写作(λI−A)v⃗=0(\lambda I-A)\vec v=0(λIA)v=0其中III为单位矩阵
此式有解的充要条件是∣λI−A∣=0|\lambda I-A|=0λIA=0,即矩阵λI−A\lambda I-AλIA的行列式为0

∣λI−A∣|\lambda I-A|λIA可以看做是关于λ\lambdaλ的一个m次多项式,记作f(λ)f(\lambda)f(λ)f(λ)f(\lambda)f(λ)称作矩阵A的特征多项式,对于矩阵A的任意一个特征值λ0\lambda_0λ0,都有f(λ0)=0f(\lambda_0)=0f(λ0)=0

Part 2 Hamilton-Cayley theorem

对于矩阵,也一样的定义多项式运算(把多项式中的x换乘矩阵A),加法就是直接对应相加,常数乘法就按位相乘,乘法是矩阵乘法,0次方是单位矩阵,它的结果仍然是一个矩阵。

显然,矩阵多项式满足交换律,即f(A)g(A)=g(A)f(A)f(A)g(A)=g(A)f(A)f(A)g(A)=g(A)f(A)成立。
简单证明:考虑某两项相乘的结果Ax×AyA^x\times A^yAx×Ay,由于前后都是A,矩阵乘法满足结合律,因此指数可以任意分配,换成Ay×AxA^y \times A^xAy×Ax也是可以的

哈密顿—凯莱定理:对于矩阵A的特征多项式f(x)f(x)f(x),满足f(A)=0f(A)=0f(A)=0

证明网上到处都有,此处就不赘述了。

Part 3 求解转移矩阵的特征多项式

回到原题,我们对于Pupil解法的转移矩阵A,求解它的特征多项式
考虑矩阵λI−A\lambda I-AλIA

它长这样:
(1)λI−A=(λ−a1−a2⋯−am−1−am−1λ⋯000−1⋯00⋮⋮⋱⋮⋮00⋯−1λ) \lambda I-A= \left( { \begin{matrix} \lambda-a_1 &amp; -a_2 &amp; \cdots &amp;-a_{m-1} &amp; -a_m \\ -1 &amp; \lambda &amp; \cdots &amp; 0 &amp;0 \\ 0 &amp; -1 &amp;\cdots &amp; 0 &amp; 0\\ \vdots &amp; \vdots &amp; \ddots &amp; \vdots &amp;\vdots \\ 0 &amp; 0 &amp; \cdots &amp; -1 &amp; \lambda \end{matrix} \tag{1} } \right) λIA=λa1100a2λ10am1001am00λ(1)

根据行列式的定义,将第一行展开
∣λI−A∣=(λ−a1)A1,1+a2×A1,2+⋯+am×A1,m|\lambda I-A|=(\lambda-a_1)A_{1,1}+a_2\times A_{1,2}+\cdots+a_m\times A_{1,m}λIA=(λa1)A1,1+a2×A1,2++am×A1,m
其中Ai,jA{i,j}Ai,j表示矩阵A的代数余子式,即挖掉第i行和第j列以后剩下的矩阵的行列式。

我们发现所有的余子矩阵都是下三角矩阵,行列式就是对角线乘积。

化简整理,可得f(λ)=∣λI−A∣=λm−∑i=0m−1am−iλif(\lambda)=|\lambda I-A|=\lambda^m-\sum\limits_{i=0}^{m-1}a_{m-i}\lambda ^if(λ)=λIA=λmi=0m1amiλi
负号都被行列式里面逆序对个数的负号消掉了。

Part 4 计算答案

我们设要求的数列ggg的初始矩阵为GGG,它是一个m行1列的矩阵(列向量),从第m行到第1行分别为g1…mg_{1\dots m}g1m(注意顺序是反的)
实际上我们想知道的gng_ngn就是矩阵An−1GA^{n-1}GAn1G的第m行第一列的值。

此时的关键就是An−1A^{n-1}An1,因为n−1n-1n1非常大,无法直接计算

然而根据前面的铺垫,我们有f(A)=0f(A)=0f(A)=0An−1A^{n-1}An1我们可以看做只有一项的一个关于A的多项式

那么根据多项式除法相关知识,可以得到An−1=P(A)f(A)+Q(A)A^{n-1}=P(A)f(A)+Q(A)An1=P(A)f(A)+Q(A),其中Q(A)Q(A)Q(A)的次数是小于f(A)f(A)f(A)的次数也就是小于m的,Q(A)Q(A)Q(A)相当于多项式An−1A^{n-1}An1对多项式f(A)f(A)f(A)取模

可能会有这样的疑问,f(A)=0f(A)=0f(A)=0怎么能作除数呢?
其实并不要紧,我们并不需要知道f(A)f(A)f(A)的实际值,我们相当于将An−1A^{n-1}An1减去了若干个f(A)f(A)f(A),将次数降低了,而结果不变。

实现上来说,由于fff的系数已知,我们可以先将式子里的矩阵A换成变量xxx,代入,利用多项式取模算出Q的系数,然后再将x换回A,这样得出来的Q的系数是相同的。并且计算Q(A)×GQ(A)\times GQ(A)×GAn−1×GA^{n-1}\times GAn1×G的结果是一样的。

为了求出Q(x)Q(x)Q(x)的系数,我们可以采用快速幂的做法,初始Q0(x)=x1Q_0(x)=x^1Q0(x)=x1,然后不断的自己与自己相乘,乘完对多项式f(x)f(x)f(x)取模
这一部分如果暴力取模,时间复杂度为O(m2log⁡n)O(m^2\log n)O(m2logn)
如果采用NTT优化多项式取模,时间复杂度为O(mlog⁡mlog⁡n)O(m\log m\log n)O(mlogmlogn)
这样求出了Q(A)Q(A)Q(A)的系数,不妨设Q(A)=∑i=0m−1diAiQ(A)=\sum\limits_{i=0}^{m-1}d_iA^iQ(A)=i=0m1diAi
要求矩阵Q(A)×GQ(A)\times GQ(A)×G的第m行第一列的值

也就是∑i=0m−1diAiG\sum\limits_{i=0}^{m-1}d_iA^iGi=0m1diAiG的第m行第一列
然而AiGA^iGAiG的第m行第一列的值就是gi+1g_{i+1}gi+1

所以gn=∑i=0m−1digi+1=∑i=0m−1dici+1g_n=\sum\limits_{i=0}^{m-1}d_ig_{i+1}=\sum\limits_{i=0}^{m-1}d_ic_{i+1}gn=i=0m1digi+1=i=0m1dici+1

还有一种情况,前m项并没有直接给出,也是通过递推得出的,暴力递推求前m项的复杂度是O(m2)O(m^2)O(m2)
考虑优化

考虑数列ggg的一般生成函数G(x)G(x)G(x)(与矩阵G不同)
转移序列aaa的一般生成函数A(x)A(x)A(x)

由于G(x)G(x)G(x)是无限长的一个序列,我们可以得到G(x)=G(x)A(x)+rG(x)=G(x)A(x)+rG(x)=G(x)A(x)+r
其中rrr是一个常数,相当于第0项

移项,可以得到G(x)=r1−A(x)G(x)={r\over 1-A(x)}G(x)=1A(x)r
在模xm+1x^{m+1}xm+1意义下多项式求逆即可
时间复杂度是O(mlog⁡m)O(m\log m)O(mlogm)

模板题([BZOJ4161] Shlw loves matrixI)

Code

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>
#include <iostream>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 4005
#define mo 1000000007
#define LL long long
using namespace std;
LL f[N],g[N],h[N],s1[N],a[N],u1[N];
int n,m;
void mul(LL *x,LL *y,LL *z)
{
	fo(i,0,2*m-2) u1[i]=0;
	fo(i,0,m-1) fo(j,0,m-1) u1[i+j]=(u1[i+j]+x[i]*y[j])%mo;
	fod(i,2*m-2,m)
	{
		fo(j,0,m) u1[i-m+j]=(u1[i-m+j]-f[j]*u1[i])%mo; 
	}
	fo(i,0,m-1) z[i]=u1[i];
}
int main()
{
	cin>>n>>m;
	fo(i,1,m) scanf("%lld",&a[i]),f[m-i]=-a[i];
	f[m]=1;
	g[1]=1;
	s1[0]=1;
	for(int t=n;t;t>>=1)
	{
		if(t&1) mul(s1,g,s1);
		mul(g,g,g);
	}
	fo(i,0,m-1) scanf("%lld",&h[i]);
	LL ans=0;
	fo(i,0,m-1) ans=(ans+s1[i]*h[i]%mo+mo)%mo;
	printf("%lld\n",ans);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值