望月Asta
2022-06-07 00:30:49
作为普通退役人 & 数学水平不足人,如果有错误欢迎来喷。
以下内容若无声明则均视为在模大质数意义下进行运算。
本文出现的题目题单
本文的一些示例代码
这里我们将会讨论线性递推的一些性质于快速求其中一项的方法。
对于一个数列
更加形式化地,称对于数列
下面如果没有说明,都会使用前者,即较为常见的定义。
对于线性递推数列
回到递推式:
也就是说,如果把
Lemma 1: 对于一组已知的
a_n = \sum_{i = 0}^{k - 1} p_i a_i ,有a_{n + m} = \sum_{i = 0}^{k - 1} p_i a_{i + m} 。
考虑从已知的线性表示
首先将
于是我们得到了用
于是我们使用快速幂的思维,令一个线性表示为
using i64 = long long;
#define add(x,y) ( (((x) += (y)) >= MOD) && ((x) -= MOD) )
int a[N],r[N];
inline void PolyMul(const int *p,const int *q,int *rs,int n) {
static int t[N << 1];memset(t,0,(n + 1) << 3);
for(int i = 0;i < n;++i) for(int j = 0;j < n;++j)
add(t[i + j],(i64)p[i] * q[j] % MOD);
for(int i = (n << 1) - 2;i >= n;--i) if(t[i])
for(int j = 0;j < n;++j) if(r[j])
add(t[i - j - 1],(i64)r[j] * t[i] % MOD);
for(int i = 0;i < n;++i) rs[i] = t[i];
}
int LinearRecurrence(int n,int k) {
static int tmp[N],res[N];
tmp[1] = res[0] = 1;
for(;n;n >>= 1) {
if(n & 1) PolyMul(tmp,res,res,k);
PolyMul(tmp,tmp,tmp,k);
}
int ans = 0;
for(int i = 0;i < k;++i)
add(ans,(i64)a[i] * res[i] % MOD);
return ans;
}
然后我们可以发现,上式是一个卷积形式,使用多项式卷积和多项式取模可以优化到
【模板】常系数齐次线性递推
从矩阵角度出发,使用 Cayley-Hamilton 定理会得到类似的结论,这里我们下面会稍微提到。
快速求出数列中的一项 【模板】常系数齐次线性递推。
加速线性递推形式的 DP 转移 [NOI2017] 泳池。
以及下面我们会提到的,搭配 Berlekamp-Massey 算法使用。
在这里我们将会讨论 Berlekamp-Massey 算法(以下简称 BM 算法)在求最短线性递推式以及其他一些方面的应用。
BM 算法会对于一个已知部分长
我们称
考虑增量法,添加一个
否则考虑
非空时,考虑使用一个特殊构造的递推式来“修正”
using i64 = long long;
#define add(x,y) ( (((x) += (y)) >= MOD) && ((x) -= MOD) )
#define red(x,y) ( (((x) -= (y)) < 0) && ((x) += MOD) )
inline i64 qpow(i64 a,i64 b = MOD - 2) {
i64 res = 1;
for(;b;b >>= 1) {
if(b & 1) res = res * a % MOD;
a = a * a % MOD;
}
return res;
}
int BerlekampMassey(const int *f,int *p,int n) {
static int lstf[N],curf[N],tmpf[N];
int k = 0,w = 0,lstk = 0;
i64 d = 0;
for(int i = 1;i <= n;++i) {
i64 tmp = 0;
for(int j = 0;j < k;++j)
add(tmp,(i64)f[i - j - 1] * curf[j] % MOD);
if((((i64)f[i] - tmp + MOD) % MOD) == 0)
continue;
if(!w) {
w = i;
d = ((i64)f[i] - tmp + MOD) % MOD;
for(int j = 1;j <= i;++j)
curf[k++] = 0;
continue;
}
for(int j = 0;j < k;++j)
tmpf[j] = curf[j];
int tmpk = k;
i64 mul = (((i64)f[i] - tmp + MOD) % MOD * qpow(d)) % MOD;
if(k < lstk + i - w)
k = lstk + i - w;
add(curf[i - w - 1],mul);
for(int j = 0;j < lstk;++j)
red(curf[i - w + j],(i64)lstf[j] * mul % MOD);
if(tmpk - i < k - w) {
for(int j = 0;j < tmpk;++j)
lstf[j] = tmpf[j];
lstk = tmpk,w = i,d = ((i64)f[i] - tmp + MOD) % MOD;
}
}
for(int i = 0;i < k;++i)
p[i] = curf[i];
return k;
}
EI 博客中介绍了一种以有理函数重建来求最短线性递推式的方法,可以到 EI 的 csdn 博客查看:最短线性递推式求解与有理函数重建-Entropy Increaser。
我们有了一个方法可以求出一个数列的最短线性递推式,现试证明对于一个未知线性递推数列需要最少多少项才可以求出一个保证准确的最短线性递推式。
令
Lemma 2:如果
r_{i - 1} 不是\{a_0 \sim a_i\} 的最短线性递推式,那么l_i \ge \max (l_{i - 1},i + 1 - l_{i - 1})
考虑反证法,假设有
于是有:
得到:
于是 Lemma 2 得证。
Lemma 3:对于一个
k 阶线性递推数列\{a\} ,求出\{a_0 \sim a_{2k - 1}\} 的最短线性递推式就是原数列最短线性递推式。
如果有一个
于是 Lemma 3 得证,我们只需要求出二倍于线性递推式长度即可。
在一些题目中,我们可以证明答案或我们要求的某个值满足线性递推关系,可以暴力预处理前面数项然后 BM 打出递推式。
[BJOI2019] 勘破神机
这道题常见的一个解法是第一类斯特林数推式子 + 扩域,但是我们可以用 BM 通过此题。
初步推导得到的式子都是
考虑原式,前缀和相当于卷上
剩下部分为:
根据 Lemma 4,我们可以求出
回看定义 2 的式子可得:
现在只需求
由于一些题使用 BM 复杂度过高,但是题目本身性质很强,可以考虑使用 BM 帮助找规律。
例题
冬至
在 BM+ 线性递推无法直接通过时,打表得到递推式满足
毛啸 关于数列递归式的一些研究
钟子谦 两类递推数列的性质和应用
郭晓旭 杨宽 线性递推关系与矩阵乘法