LG5487 【模板】线性递推+BM算法
【模板】线性递推+BM算法
给出一个数列 \(P\) 从 \(0\) 开始的前 \(n\) 项,求序列 \(P\) 在\(\bmod~998244353\) 下的最短线性递推式,并在 \(\bmod~ 998244353\) 下输出 \(P_m\)。
\(m\leq 10^9,1\leq n\leq 10000\)
保证递推式最长不超过 \(5000\)。
Berlekamp-Massey 算法
Berlekamp-Massey 算法,常简称为 BM 算法,是用来求解一个数列的最短线性递推式的算法。
BM 算法可以在 \(O(n^2)\) 的时间内求解一个长度为 \(n\) 的数列的最短线性递推式。
基本定义
对于数列 \(A=\{a_1,a_2,\dots,a_n\}\),我们定义数列 \(R=\{r_1,r_2,\dots,r_m\}\) 为其线性递推式当且仅当
\]
注意,无论你习惯从左到右写数列还是从右到左,这里的数列和线性递推式的位置对应关系是反着的。
所有可能的线性递推式 \(R\) 中长度 \(m\) 最小的叫做 \(A\) 的最短线性递推式。
算法流程
假设我们已经求得了 \(\{a_1,a_2,\dots,a_{i-1}\}\) 的最短线性递推式 \(\{r_1,r_2,\dots,r_m\}\),那么如何求得 \(\{a_1,a_2,\dots,a_i\}\) 的最短线性递推式?
定义 \(\{a_1,a_2,\dots,a_{i-1}\}\) 的最短线性递推式 \(\{r_1,r_2,\dots,r_m\}\) 为当前递推式,记递推式被更改的次数为 \(cnt\),第 \(i\) 次更改后的递推式为 \(R_i\),那么当前递推式应当为 \(R_{cnt}\)。特别地,定义 \(R_0=\varnothing\)。
我们对每个版本的 \(R\),记一个表示差异量的 \(\Delta_i\),满足
\]
显然若 \(\Delta_i=0\),那么当前递推式就是 \(\{a_1,a_2,\dots,a_i\}\) 的最短线性递推式。
否则我们认为 \(R_{cnt}\) 在 \(a_i\) 处出错了,令 \(fail_i\) 为 \(R_i\) 最早的出错位置,则有 \(fail_{cnt}=i\)。考虑对 \(R_{cnt}\) 进行修改,使其变为 \(R_{cnt+1}\),并在 \(a_i\) 处同样成立。
若当前 \(cnt=0\),说明 \(a_i\) 是第一个非零元素,直接将 \(R_1\) 置为 \(\{ \underbrace{0,0,\dots,0}_{i} \}\) 即可,因为不可能用之前的数递推出 \(a_i\)。
否,即 \(cnt>0\),则考虑用 \(R_{cnt}\) 之前失败的递推式将这个 \(\Delta_i\) 加回去(\(a=\sum+\Delta\))。我们希望得到数列 \(R'=\{r'_1,r'_2,...,r'_{m'}\}\),使得
\sum_{j=1}^{m'}r'_ja_{i-j}=\Delta_i
\]
如果能够找到这样的数列 \(R'\),那么令
\]
即可。这里加号表示各位对应相加。
在之前失败的递推式中任选一个 \(R_p\),尝试在它的基础上修改,在 \(i\) 的位置上构造出 \(\Delta_{fail_p}\)(这里的 \(\Delta\) 是对应 \(R_p\) 版本的),记得到的结果为 \(R_p'\),那么
\]
考虑如何构造 \(R'_p\) 。将 \(R_p\) 的元素全部变成它的相反数,再在前面补上一个 \(1\) , \(\Delta_{fail_p}\) 就到 \(fail_p+1\) 位置上来了。前面再补上 \(i-fail_p-1\) 个 \(0\),\(\Delta_{fail_p}\) 就到 \(i\) 位置上来了。于是
\]
这里乘号表示顺次连接。
又因为 \(R_p\) 在 \(fail_p\) 前的 \(\Delta=0\),所以我们构造出来的 \(R'\) 是满足第一条约束的。
为了保证得到的递推式长度最短,我们需要选取恰当的 \(R_p\)。容易看出,得到的 \(R_{cnt+1}\) 的长度为 \(\max(i-fail_p+m_p,m)\)。于是记录 \(m_p-fail_p\) 最短的递推式作为 \(R_p\) 。
至此我们完成了 BM 算法的理论部分,在最坏情况下,我们可能需要对数列进行 \(O(n)\) 次修改,因此该算法的时间复杂度为 \(O(n^2)\)。
经验之谈
用 BM 得到的最短递推式长度最好要明显小于 \(n\) 的一半,否则需要再打些表。
为什么?因为若长度为 \(\frac n 2\),可以看做 \(\frac n 2\) 个变量列出 \(\frac n 2\) 个方程,总能找到解。所以一个随机数列解出的最短递推式长度就是 \(\frac n 2\) 左右。发生了这样的情况说明原数列很可能并没有一定的规律,即递推式大概率对之后的数据不适用。
另外因为计算中涉及除法,所以 BM 在实数域内求解可能有一定的精度误差。
#include<bits/stdc++.h>
using namespace std;
template<class T> T read(){
T x=0,w=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-') w=-w;
for(;isdigit(c);c=getchar()) x=x*10+c-'0';
return x*w;
}
template<class T> T read(T&x){
return x=read<T>();
}
#define co const
#define il inline
typedef long long LL;
co int mod=998244353;
il int add(int a,int b){
return (a+=b)>=mod?a-mod:a;
}
il int mul(int a,int b){
return (LL)a*b%mod;
}
il int fpow(int a,int b){
int ans=1;
for(;b;b>>=1,a=mul(a,a))
if(b&1) ans=mul(ans,a);
return ans;
}
namespace linear{
typedef vector<int> polynomial;
void num_trans(polynomial&a,int dir){
int lim=a.size();
static vector<int> rev,w[2];
if(rev.size()!=lim){
rev.resize(lim);
int len=log2(lim);
for(int i=0;i<lim;++i) rev[i]=rev[i>>1]>>1|(i&1)<<(len-1);
for(int dir=0;dir<2;++dir){
static co int g[2]={3,332748118};
w[dir].resize(lim);
w[dir][0]=1,w[dir][1]=fpow(g[dir],(mod-1)/lim);
for(int i=2;i<lim;++i) w[dir][i]=mul(w[dir][i-1],w[dir][1]);
}
}
for(int i=0;i<lim;++i)if(i<rev[i]) swap(a[i],a[rev[i]]);
for(int step=1;step<lim;step<<=1){
int quot=lim/(step<<1);
for(int i=0;i<lim;i+=step<<1){
int j=i+step;
for(int k=0;k<step;++k){
int t=mul(w[dir][quot*k],a[j+k]);
a[j+k]=add(a[i+k],mod-t),a[i+k]=add(a[i+k],t);
}
}
}
if(dir){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=mul(a[i],ilim);
}
}
polynomial poly_inv(polynomial a,int n){
polynomial b(1,fpow(a[0],mod-2));
if(n==1) return b;
int lim=2;
for(;lim<n;lim<<=1){
polynomial a1(a.begin(),a.begin()+lim);
a1.resize(lim<<1),num_trans(a1,0);
b.resize(lim<<1),num_trans(b,0);
for(int i=0;i<lim<<1;++i) b[i]=mul(add(2,mod-mul(a1[i],b[i])),b[i]);
num_trans(b,1),b.resize(lim);
}
a.resize(lim<<1),num_trans(a,0);
b.resize(lim<<1),num_trans(b,0);
for(int i=0;i<lim<<1;++i) b[i]=mul(add(2,mod-mul(a[i],b[i])),b[i]);
num_trans(b,1),b.resize(n);
return b;
}
polynomial operator/(polynomial f,polynomial g){
int n=f.size()-1,m=g.size()-1;
reverse(g.begin(),g.end()),g.resize(n-m+1),g=poly_inv(g,n-m+1);
reverse(f.begin(),f.end()),f.resize(n-m+1);
int lim=1<<int(ceil(log2((n-m)<<1|1)));
f.resize(lim),num_trans(f,0);
g.resize(lim),num_trans(g,0);
for(int i=0;i<lim;++i) f[i]=mul(f[i],g[i]);
num_trans(f,1),f.resize(n-m+1);
return reverse(f.begin(),f.end()),f;
}
polynomial operator%(polynomial f,polynomial g){
int n=f.size()-1,m=g.size()-1;
polynomial q=f/g;
int lim=1<<int(ceil(log2(n+1)));
q.resize(lim),num_trans(q,0);
g.resize(lim),num_trans(g,0);
for(int i=0;i<lim;++i) q[i]=mul(q[i],g[i]);
num_trans(q,1);
for(int i=0;i<m;++i) f[i]=add(f[i],mod-q[i]);
return f.resize(m),f;
}
int n,k;
void mul_mod(polynomial&a,polynomial b,co polynomial&p){
static co int lim=1<<int(ceil(log2(2*k-1)));
a.resize(lim),b.resize(lim);
num_trans(a,0),num_trans(b,0);
for(int i=0;i<lim;++i) a[i]=mul(a[i],b[i]);
num_trans(a,1),a.resize(2*k-1);
a=a%p;
}
void main(int _n,int _k,co vector<int>&_a,co vector<int>&_f){
n=_n,k=_k;
polynomial a(k),f(k);
for(int i=1;i<=k;++i) a[k-i]=mod-_a[i];
a.push_back(1);
for(int i=0;i<k;++i) f[i]=_f[i];
polynomial rmd(1,1),tmp(2);tmp[1]=1;
for(;n;n>>=1,mul_mod(tmp,tmp,a))
if(n&1) mul_mod(rmd,tmp,a);
int ans=0;
for(int i=0;i<k;++i) ans=add(ans,mul(rmd[i],f[i]));
printf("%d\n",ans);
}
}
vector<int> ber_ma(vector<int> f){
vector<int> lst,cur;
int lsfa,lsdel;
for(int i=0;i<(int)f.size();++i){
int del=f[i];
for(int j=1;j<(int)cur.size();++j)
del=add(del,mod-mul(cur[j],f[i-j]));
if(!del) continue;
if(!cur.size()){
cur.resize(i+1),lsfa=i,lsdel=del;
continue;
}
int alph=mul(del,fpow(lsdel,mod-2));
vector<int> nw(i-lsfa);
nw.push_back(alph);
for(int j=1;j<(int)lst.size();++j)
nw.push_back(mul(alph,mod-lst[j]));
if(nw.size()<cur.size()) nw.resize(cur.size());
for(int j=1;j<(int)cur.size();++j)
nw[j]=add(nw[j],cur[j]);
if(i-lsfa+(int)lst.size()>=(int)cur.size())
lst=cur,lsfa=i,lsdel=del;
cur=nw;
}
return cur;
}
int main(){
int n=read<int>(),m=read<int>();
vector<int> f(n);
for(int i=0;i<n;++i) read(f[i]);
vector<int> a=ber_ma(f);
for(int i=1;i<(int)a.size();++i) printf("%d ",a[i]);
puts("");
if(m<=n) {printf("%d\n",f[m]);return 0;}
linear::main(m,a.size()-1,a,f);
return 0;
}
线性递推式是 base 1 的,用 vector 存的话代码有点奇怪。
LG5487 【模板】线性递推+BM算法的更多相关文章
- [模板]线性递推+BM
暴力版本: #include<bits/stdc++.h> #define mod 998244353 using namespace std; typedef long long int ...
- 模板 - 线性递推BM
模数是998244353的话好像NTT可以更快. #include<bits/stdc++.h> using namespace std; typedef long long ll; co ...
- 线性递推BM模板
#include <cstdio> #include<iostream> #include <cstring> #include <cmath> #in ...
- Berlekamp Massey算法求线性递推式
BM算法求求线性递推式 P5487 线性递推+BM算法 待AC. Poor God Water // 题目来源:ACM-ICPC 2018 焦作赛区网络预赛 题意 God Wate ...
- BM求线性递推模板(杜教版)
BM求线性递推模板(杜教版) BM求线性递推是最近了解到的一个黑科技 如果一个数列.其能够通过线性递推而来 例如使用矩阵快速幂优化的 DP 大概都可以丢进去 则使用 BM 即可得到任意 N 项的数列元 ...
- 【模板】BM + CH(线性递推式的求解,常系数齐次线性递推)
这里所有的内容都将有关于一个线性递推: $f_{n} = \sum\limits_{i = 1}^{k} a_{i} * f_{n - i}$,其中$f_{0}, f_{1}, ... , f_{k ...
- HDU - 6172:Array Challenge (BM线性递推)
题意:给出,三个函数,h,b,a,然后T次询问,每次给出n,求sqrt(an); 思路:不会推,但是感觉a应该是线性的,这个时候我们就可以用BM线性递推,自己求出前几项,然后放到模板里,就可以求了. ...
- 2018 焦作网络赛 L Poor God Water ( AC自动机构造矩阵、BM求线性递推、手动构造矩阵、矩阵快速幂 )
题目链接 题意 : 实际上可以转化一下题意 要求求出用三个不同元素的字符集例如 { 'A' .'B' .'C' } 构造出长度为 n 且不包含 AAA.BBB CCC.ACB BCA.CAC CBC ...
- 牛客多校第九场 A The power of Fibonacci 杜教bm解线性递推
题意:计算斐波那契数列前n项和的m次方模1e9 题解: $F[i] – F[i-1] – F[i-2] = 0$ $F[i]^2 – 2 F[i-1]^2 – 2 F[i-2]^2 + F[i-3] ...
随机推荐
- 设置程序崩溃时产生 core 文件的配置
/* 不限制 core 文件的大小 */ ulimit -c unlimited /* 使用 pid 进行命名 */ echo " > /proc/sys/kernel/core_us ...
- Block chain 1_The Long Road to Bitcoin
The path to Bitcoin is littered with the corpses of failed attempts. I've compiled a list of about a ...
- 【转帖】微软全新Windows 10X细节一览
微软全新Windows 10X细节一览 https://www.cnbeta.com/articles/tech/906241.htm windows NT之后 又一大改进 今年的Surface发布会 ...
- JQuery 总结篇
一.基础知识 1.jq的使用 在script块中: 方式1:$(function(){ $("[使用选择器] ...
- min-25筛学习笔记
Min_25筛简介 \(\text{min_25}\)筛是一种处理一类积性函数前缀和的算法. 其中这类函数\(f(x)\)要满足\(\sum_{i=1}^{n}[i\in prime]\cdot f( ...
- Codeforces 1239B. The World Is Just a Programming Task (Hard Version)
传送门 这一题好妙啊 首先把括号序列转化成平面直角坐标系 $xOy$ 上的折线,初始时折线从坐标系原点 $(0,0)$ 出发 如果第 $i$ 个位置是 '(' 那么折线就往上走一步($y+1$),否则 ...
- Java泛型 - 返回父类的子类
一.栗子 public class GenericityInher { //error: Type mismatch: cannot convert from ArrayList<Child&g ...
- 配置Setting.xml文件提高maven更新下载jar包速度
<?xml version="1.0" encoding="UTF-8"?> <settings xmlns="http://mav ...
- Excel默认去除开头的0
用户反映打开的.xls文档打开时,excel会默认把某些以0开头零件号去零,导致数据丢失. 解决办法: 先用记事本打开,然后把EXCEL的单元格格式设为文本格式,再把数据复制过去就可以了. 或者先打开 ...
- Objective-C和 C++ 混编的要点
Using C++ With Objective-C苹果的Objective-C编译器允许用户在同一个源文件里自由地混合使用C++和Objective-C,混编后的语言叫Objective-C++.有 ...