分治FFT学习笔记
分治与其优化
前置知识 :
快速傅里叶变换,快速数论变换,多项式求逆,CDQ分治,生成函数。
【(蒟蒻的学习笔记) 】【或者可以自行去看其他更好的博客讲解】
进入正题
- 引入问题
已知函数在的函数值,求给定的的函数在的值,其中定义如下:
其中,。(答案对取模)
数据范围:,其余均小于模数。
- 我会暴力!
其实,根据定义式,直接就可以写出(实际是)的复杂度的算法。
但是显然不行。
- 我会!
这个其实很容易看出是一个卷积式子,但是对于每一个,我们不可能重新的去算一次,因为那样的话复杂度会退化成,还不如暴力了,所以我们要考虑优化。
如果我们已经知道了,那么我们可以先计算出这些已知的对于未知部分的贡献,我们看原式,发现对于一个已知的,对于后面的都只会有的贡献。
所以对于一个当前的区间我们已知,那么它对于的贡献,可以用如下方法快速计算:
我们令,再令,那么我们再令(卷积,相当于),那么对于的其中的贡献就为,这个就是用实现了。
那么对于整个区间,我们分治下去,先计算,这时你已经知道前半部分的值,然后计算贡献,加到后半部分,然后去算的值,这也就是分治的过程。
总的复杂度为(总共递归层,每层元素个,为,所以总复杂度为)。
Luogu 模板
#include<cstdio>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
const int M=4e5+10;
const ll mod=998244353ll,G=3;//模数与原根
ll inv[M],g[M],f[M];
int R[M],lg,sze;
ll fpow(ll a,ll b){
ll ans=1;
for(;b;b>>=1,a=(a*a)%mod){
if(b&1) ans=(ans*a)%mod;
}
return ans;
}
void NTT(ll *a,int n,int f){
for(int i=0;i<n;i++)if(i<R[i])swap(a[i],a[R[i]]);
for(int i=2;i<=n;i<<=1){
int now=i>>1;
ll wn=inv[i];//现场算或者预处理
// fpow(G,(mod-1)/i);
for(int j=0;j<n;j+=i){
ll w=1,x,y;
for(int k=j;k<j+now;k++,w=(w*wn)%mod){
x=a[k],y=w*a[k+now]%mod;
a[k]=(x+y)%mod;a[k+now]=((x-y)%mod+mod)%mod;
}
}
}
if(f==-1){
ll Inv=fpow(n,mod-2);
for(int i=0;i<=n;i++) a[i]=(a[i]*Inv)%mod;
for(int i=1;i<=(n>>1);i++)swap(a[i],a[n-i]);
}
}
void calc(ll *a,ll *b,int n){
NTT(a,n,1);NTT(b,n,1);
for(int i=0;i<=n;i++)a[i]=(a[i]*b[i])%mod;
NTT(a,n,-1);
}
ll A[M],B[M];
void cdq(int l,int r){
if(l==r) return;
int mid=l+r>>1;
cdq(l,mid);
int up=r-l-1;
// for(lg=0,sze=1;sze<=(up<<1);sze<<=1)++lg;--lg;
for(lg=0,sze=1;sze<=up;sze<<=1)++lg;--lg;//其实只用的到区间长度,实际却有两倍,所以保险用两倍。
for(int i=0;i<sze;i++) R[i]=(R[i>>1]>>1)|((i&1)<<lg),A[i]=B[i]=0;
// memset(A,0,sizeof(A));memset(B,0,sizeof(B));
// fill(A,A+sze,0);fill(B,B+sze,0);//这两种方法赋值fill会比memset快一些(因为fill确定了大小)
for(int i=l;i<=mid;i++) A[i-l]=f[i];
for(int i=1;i<=r-l;i++) B[i-1]=g[i];
calc(A,B,sze);
for(int i=mid+1;i<=r;i++) f[i]=(f[i]+A[i-l-1]%mod)%mod;
cdq(mid+1,r);
}
int n;
int main(){
scanf("%d",&n);f[0]=1;
for(int i=1;i<n;i++)scanf("%lld",&g[i]);
for(int i=2,up=n<<2;i<=up;i<<=1)inv[i]=fpow(G,(mod-1)/i);
cdq(0,n-1);
for(int i=0;i<n;i++) printf("%lld%c",f[i],i==n-1?'\n':' ');
return 0;
}
有没有更加优秀的做法呢?
有,但是只有在取模的意义下,我们只需将式子变形即可。
我们令为的生成函数,为的生成函数。
那么可以得知:
那么求和的卷积就有:
我们发现是不是和很像,所以我们可以知道那个卷积式可以写成,也就是,那么我们最后求的答案变为下面这个式子:
那么我们直接使用多项式求逆即可解决,复杂度
博主也是才学习,有错误或者疑惑可以提出,大佬带带蒟蒻