此博客是抄论文的,你可以认为是转载的
1.线性递推数列
有限数列显然是线性递推数列。
无限数列
设其生成函数为
那么如果
能被表示为
的形式,其中
,则
是线性递推数列。
常数项为
是因为递推式你要让
来递推出
所以常数项需要为
。
能这样表示是因为线性递推实质上就是
,其中
是我们的线性递推式。
对于一个线性递推数列
,假设他前
项的最短递推式是
,长度为
,那么如果
不是前
项的最短递推式,那么有
,且这个等号是可以通过构造取到的。
首先
显然,如果
的话:
实在不知道怎么用语言表示交换和号。
也就是说如果
那么原来的递推式必可以继续用。
接下来我们给出在
不是前
项的递推式时
的构造方案,也就是
算法。
也就是说每次增长递推式,我们都可以用这个方法使得增长时
,不增长时
。
因为上文证明了
,所以这个算法对于有限长度的数列求出的递推式一定是最短的。
对于无限长的数列,
所以我们只需要取最短递推式长度的两倍即可。
边界情况:第一次增长递推式的时候,
应该是第一个非
元素,那么
个
即为前
个数的最短递推式,也就是根本没有递推,同时也满足
。
这里需要纠正一个错误观念,一个最短线性递推式的最后一项是可以为
的,因为假如一个长度为
的递推式
对于
不成立,我们需要在递推式的最后补一个
,这样
就不在递推式成立的范围内。
也就是说一个线性递推数列被表示为
的形式,则它的递推式的长度是
的次数和
的次数加一取
。
线性递推求第
项:
代码:luogu【模板】Berlekamp-Massey算法
(真的很短。)
#include<bits/stdc++.h>
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define maxn 10005
#define pb push_back
#define vi vector<int>
#define mod 998244353
using namespace std;
int n,m,a[maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod;return r; }
vi BM(int *a,int n){ // a[0 ~ n] is used
vi r(1,1),p,t;int lt;
rep(i,0,n){
int d=0;
rep(j,0,r.size()-1) d = (d + r[j] * 1ll * a[i-j]) % mod;
if(!d) continue;
t = r;
r.resize(max(r.size() , i+2-(r.size()-(r.size()!=0))));
rep(j,0,p.size()-1) r[j+i-lt] = (r[j+i-lt] - 1ll * d * p[j]) % mod;
int iv = Pow(d , mod-2);
p = t , lt = i;
rep(i,0,p.size()-1) p[i] = 1ll * p[i] * iv % mod;
}
return r; // a \times r = const
}
typedef vi poly;
poly Mul(const poly &A,const poly &B,const poly &P){
poly r(A.size() + B.size() - 1);
rep(i,0,A.size()-1) rep(j,0,B.size()-1) r[i+j] = (r[i+j] + 1ll * A[i] * B[j]) % mod;
per(i,r.size()-1,P.size()-1) if(r[i]){// in this problem P[P.size()-1] = 1 , so there is no need to getinv.
int t = r[i];
rep(j,1,P.size())
r[i-j+1] = (r[i-j+1] - 1ll * P[P.size()-j] * t) % mod;
}
r.resize(P.size()-1);
return r;
}
int main(){
scanf("%d%d",&n,&m);
rep(i,0,n-1) scanf("%d",&a[i]);
vi P = BM(a,n-1);
rep(i,1,P.size()-1) printf("%d%c",(mod-(P[i]+mod)%mod) % mod," \n"[i==P.size()-1]);
reverse(P.begin(),P.end());
poly r(1,1),t(2,0);
t[1] = 1;
for(;m;m>>=1,t=Mul(t,t,P)) if(m&1)
r = Mul(r,t,P);
int ans = 0;
rep(i,0,r.size()-1) ans = (ans + 1ll * r[i] * a[i]) % mod;
printf("%d\n",(ans+mod)%mod);
}
向量序列的最短递推式:
矩阵的零化多项式:使得矩阵
:
矩阵的最小多项式:
零化多项式中次数最低的多项式。
如何求矩阵的最小多项式:
直接求
的最短递推式即可,对于稀疏矩阵可以做到
。
BM与矩阵的特征多项式:
特征多项式是矩阵的一个零化多项式,
而
求出的最小多项式也是矩阵的一个零化多项式
最小多项式是特征多项式的一个因式,因为如果不是则可以做带余除法得到余式为更小的零化多项式。
对于一个线性递推问题,我们可以通过零化多项式得到线性递推式,所以在解决线性递推时可以找最小多项式(用BM),也可以求特征多项式。
但是特征多项式的最经典的解法是根据定义
来求行列式后拉格朗日插值求出多项式,
相较于
感觉没有什么竞争力,尽管特征多项式有
的巧妙解法,但是这个解法无法计算出线性递推的前
项(就是不能用递推式的部分),有了前
项那为什么不用
呢?
综上在信息学奥赛的当前时代最小多项式完爆特征多项式,很多打着特征多项式的旗子的题目都可以用BM解决。
接下来我们看一个例题:
「2018 集训队互测 Day 1」完美的旅行
个点的有向图,一次旅行是走
步,愉悦值为起点和终点的编号
和。
多次旅行的愉悦值是每次旅行的
和,对于所有的愉悦值
和
的总步数,求愉悦值为
总步数为
的多次旅行方案数。
先求
为集合
的超集的方案数
,然后用
一次就可以求出
为集合
的方案数。
为集合
的超集的一次旅行走了
步,可以发现就是邻接矩阵
的
次方的一些位置的和。
大小为
的矩阵一定有次数
的特征多项式,所以一次旅行的走了
步的那些答案就是一个
阶线性递推。
假设一次旅行的生成函数为
,则多次旅行的生成函数为
因为
为
阶递推,所以
,其中
次数
,
次数
。
,分子次数可以为
,所以
是
阶线性递推。