BM+线性递推 学习笔记
BM
BM 用于构造出给定数列的最短递推式,常用来打表
设给出的数列为
使用增量法构造. 一开始的时候,设第
假设现在的递推系数的版本为
如果
即
于是我们只要让
为了让递推系数最短,我们需要取让
线性递推
求出递推式之后需要快速计算第
考虑递推式的特征多项式
我们知道
我们有结论
我们有不同的方式来得出这个结论,常见的一种是通过线性代数的方法. 考虑转移矩阵
其特征多项式为
通过展开第一行来计算就得到
Cayley-Hamilton 定理指出
不过我线代水平8太行,不是很想证(
于是我们计算出
这样我们就得到
实现的时候由于对
代码是暴力取模+BM,带多项式取模的快速幂靠着半在线卷积的速度完全不优化就进luogu第二页了(
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int mod=998244353;
int n,m;
vector<int>a,f;
int qmod1(int x){return x>=mod?x-mod:x;}
int qmod2(int x){return x+(x>>31&mod);}
int qpower(int a,int b)
{
int ans=1;for(;b;b>>=1,a=1ll*a*a%mod)if(b&1)ans=1ll*ans*a%mod;
return ans;
}
void print(const vector<int>&a)
{
for(int i=0;i<a.size();i++)cout<<a[i]<<" ";puts("");
}
vector<int>BM(const vector<int>&a)
{
vector<int>ans,tmp,nw;int delta,tdelta,fail,tfail;
for(int i=0;i<a.size();i++)
{
delta=a[i];
for(int j=0;j<ans.size();j++)
delta=qmod2(delta-1ll*ans[j]*a[i-j-1]%mod);
if(!delta)continue;
fail=i;
if(!ans.size()){ans.resize(i+1),tmp.resize(i);tfail=i,tdelta=delta;continue;}
nw=ans;
int t=1ll*delta*qpower(tdelta,mod-2)%mod;
if(ans.size()<i-tfail+tmp.size())ans.resize(i-tfail+tmp.size());
ans[i-tfail-1]=qmod1(ans[i-tfail-1]+t);
for(int j=0;j<tmp.size();j++)ans[i-tfail+j]=qmod2(ans[i-tfail+j]-1ll*t*tmp[j]%mod);
if(fail-nw.size()>tfail-tmp.size()){tdelta=delta,tfail=fail,tmp=nw;}
}
return ans;
}
void Mod(vector<int>&a,const vector<int>&b)
{
for(int i=a.size()-1;i>=b.size();i--)
for(int j=0;j<b.size();j++)
a[i-j-1]=(a[i-j-1]+1ll*a[i]*b[j])%mod;
a.resize(b.size());
}
vector<int>operator *(const vector<int>&a,const vector<int>&b)
{
vector<int>c;c.resize(a.size()+b.size()-1);
for(int i=0;i<a.size();i++)
for(int j=0;j<b.size();j++)
c[i+j]=(c[i+j]+1ll*a[i]*b[j])%mod;
return c;
}
vector<int>calc(int n,const vector<int>&m)
{
vector<int>ans;ans.push_back(1);vector<int>a;a.push_back(0),a.push_back(1);
for(;n;n>>=1,a=a*a,Mod(a,m))if(n&1)ans=ans*a,Mod(ans,m);
return ans;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0,x;i<n;i++)scanf("%d",&x),a.push_back(x);
f=BM(a);
for(int i=0;i<f.size();i++)printf("%d ",f[i]);puts("");
f=calc(m,f);int ans=0;
for(int i=0;i<f.size();i++)ans=(ans+1ll*f[i]*a[i])%mod;
cout<<ans<<endl;
}
求一段点值
求出
我们已经可以求出
根据前面的推导,显然也有
于是做一次减法卷积即可.
2020.10.21 upd
怎么我昨天写完今天就有新科技了啊
2020年,在众多毒瘤的努力下,多项式取模成为时代的眼泪
300iq:well-known in Russia
EItxdy
一个更加快速且简单的线性递推方法
EI 他们好像还没研究完,所以这个东西能不能加特技还不清楚
我们知道线性递推的生成函数可以写为
也就是特征多项式的系数翻转.
我们需要求的就是
那么我们可以递归计算答案:
朴素实现(上下都做长为
然而对于可以做 FFT 的情况下我们可以继续优化常数. 以下用
-
用
\bold{DFT}[Q(z)] 计算\bold{DFT}[Q(-z)] 我们有
\bold{DFT}[Q(-z)]_i=\sum_{k}q_k(-1)^k\omega_{2n}^{ki}=\sum_{k}q_k\omega_{2n}^{(i+n)k}=\bold{DFT}[Q(z)]_{i\oplus n} 这样就只要做
4 次长为2n 的 FFT,时间优化到\dfrac{4}{3}M(n) . -
只提取奇/偶项
如果我们要提取
A 的偶次幂项系数,也就是在求B(z)=\dfrac{A(z)+A(-z)}{2}=A_{even}(z^2) 的系数,那么很显然有\widehat{B}_k=\dfrac{\widehat{A}_k+\widehat{A}_{k\oplus n}}{2} ,并且可以知道\widehat{B} 的前n 项就是A_{even} 做长为n 的\bold{DFT} 的结果:(\widehat{A}_{even}^{(n)})_i=\sum_{k}A_{2k}\omega_n^{ki}=\sum_{k}B_k\omega_{2n}^{ki}=\widehat{B}^{(2n)}_i 对奇数的情况也差不多,即计算
B(z)=\dfrac{A(z)-A(-z)}{2}=zA_{odd}(z^2) 的系数,那么这时就有\widehat{B}=\dfrac{\widehat{A}_k-\widehat{A}_{k\oplus n}}{2} ,并且可以导出\widehat{A}_{odd} 和\widehat{B} 之间的关系:(\widehat{A}_{odd}^{(n)})_i=\sum_{k}A_{2k+1}\omega_n^{ki}=\sum_{k}B_k\omega_{2n}^{(k-1)i}=\omega_{2n}^{-i}\widehat{B}^{(2n)}_i 因此做
\bold{IDFT} 的时候可以把长度缩短为n ,这样需要做2 次长为2n 的 FFT 和两次长为n 的 FFT,相当于一次M(n) -
不做
\bold{IDFT} 我们来考虑只维护
P,Q 做\bold{DFT} 的结果. 我们在前一次递归中其实已经算出了P 和Q 的长为n 的\bold{DFT} 的结果,我们可以利用它得到长为2n 的\bold{DFT} 的结果:\widehat{A}^{(2n)}_{2i}=\sum_{k}A_k\omega_{2n}^{2ik}=\sum_{k}A_k\omega_n^{ik}=\widehat{A}^{(n)}_i \widehat{A}_{2i+1}^{(2n)}=\sum_kA_k\omega_{2n}^{(2i+1)k}=\sum_kA_k\omega_{2n}^k\omega_{n}^i 于是把
\widehat{A}^{(n)} \bold{IDFT} 回去之后点乘\omega_{2n}^k 再\bold{DFT} 就得到了\widehat{A}^{(2n)} 的奇数项,使用了两次长为n 的 FFT. 显然这一步不能继续优化复杂度了,不然我们就会得到一个低于\Theta(n\log n) 的\bold{DFT} 方法. 于是我们就得到一个每次递归只需要4 次长为n 的 FFT 的方法,相当于\dfrac{2}{3}M(n) .
跑得飞快,抢到了暂时的最优解(
void doublen(int a[],int b[],int l)
{
copy(a,a+l,b);
Poly::IDFT(b,l);
for(int i=0;i<l;i++)b[i]=1ll*b[i]*Poly::w[l+i]%mod;
Poly::DFT(b,l);
for(int i=l-1;i>=0;i--)b[i<<1|1]=b[i];
for(int i=0;i<l;i++)b[i<<1]=a[i];
}
int p[N],q[N];
int main()
{
// freopen("data.txt","r",stdin);
int n=getin(),K=getin();q[0]=1;
for(int i=1;i<=K;i++)q[i]=qmod2(-getin());
for(int i=0;i<K;i++)p[i]=qmod2(getin());
Poly::mul(q,p,p,K);
int l=1;while(l<=K)l<<=1;
// fill(p+K,p+(l<<1),0),fill(q+K+1,q+(l<<1),0);
int isfirst=1;
static int tmpp[N],tmpq[N];
static int iw[N];iw[0]=1,iw[1]=qpower(3,mod-1-((mod-1)>>t));
for(int i=2;i<l;i++)iw[i]=1ll*iw[i-1]*iw[1]%mod;//我嫌麻烦直接在这要把单位根的逆算出来了(
while(n>=K)
{
if(isfirst)//注意第一次递归的时候要特判
{
isfirst=0;
copy(p,p+K,tmpp),fill(tmpp+K,tmpp+(l<<1),0);
copy(q,q+K+1,tmpq),fill(tmpq+K+1,tmpq+(l<<1),0);
Poly::DFT(tmpp,l<<1),Poly::DFT(tmpq,l<<1);
}
else
{
doublen(p,tmpp,l),doublen(q,tmpq,l);
}
if(~n&1)
{
int iv2=qpower(2,mod-2);
for(int i=0;i<l;i++)p[i]=(1ll*tmpp[i]*tmpq[i^l]+1ll*tmpp[i^l]*tmpq[i])%mod*iv2%mod;
}
else
{
int iv2=qpower(2,mod-2);
for(int i=0;i<l;i++)p[i]=(1ll*tmpp[i]*tmpq[i^l]+1ll*(mod-tmpp[i^l])*tmpq[i])%mod*iv2%mod;
for(int i=0;i<l;i++)p[i]=1ll*p[i]*iw[i]%mod;
}
for(int i=0;i<l;i++)q[i]=1ll*tmpq[i]*tmpq[i^l]%mod;
n>>=1;
}
if(!isfirst){Poly::IDFT(p,l),Poly::IDFT(q,l);}//如果发生过递归那么p,q里存的是DFT的值,需要做一次IDFT
Poly::Inv(q,q,K),Poly::mul(p,q,p,K);//n<K后直接做除法计算
cout<<p[n]<<endl;
}
计算 z^n\bmod F(z)
我们还是来考虑多项式带余除法的式子:
翻转系数
两边用
由于
其中
由于
于是关键在于计算
其中
不过这玩意的空间好像是