快速傅里叶变换,快速数论变换,多项式求逆,CDQ分治,生成函数。
【(蒟蒻的学习笔记) F F T \rm FFT FFT N T T \rm NTT NTT】【或者可以自行去看其他更好的博客讲解】
已知函数 g ( x ) g(x) g(x)在 x ∈ [ 1 , n − 1 ] x\in [1,n-1] x∈[1,n−1]的函数值,求给定的 f ( x ) f(x) f(x)的函数在 x ∈ [ 1 , n − 1 ] x\in [1,n-1] x∈[1,n−1]的值,其中 f ( x ) f(x) f(x)定义如下:
f ( x ) = ∑ y = 1 x f ( x − y ) g ( y ) f(x)=\sum\limits_{y=1}^xf(x-y)g(y) f(x)=y=1∑xf(x−y)g(y)
其中, f ( 0 ) = 1 f(0)=1 f(0)=1。(答案对 998244353 998244353 998244353取模)
数据范围: 2 ≤ n ≤ 1 0 5 2\leq n\leq10^5 2≤n≤105,其余均小于模数。
其实,根据定义式,直接就可以写出 O ( n 2 ) O(n^2) O(n2)(实际是 n × ( n − 1 ) 2 \frac{n\times(n-1)}{2} 2n×(n−1))的复杂度的算法。
但是显然不行。
这个其实很容易看出是一个卷积式子,但是对于每一个 f ( x ) f(x) f(x),我们不可能重新的去算一次 F F T \rm FFT FFT,因为那样的话复杂度会退化成 O ( n 2 l o g n ) O(n^2logn) O(n2logn),还不如暴力了,所以我们要考虑优化。
如果我们已经知道了 f ( 1 ) ∼ f ( n 2 ) f(1)\sim f(\frac{n}{2}) f(1)∼f(2n),那么我们可以先计算出这些已知的对于未知部分 f ( n 2 + 1 ) ∼ f ( n ) f(\frac{n}{2}+1)\sim f(n) f(2n+1)∼f(n)的贡献,我们看原式,发现对于一个已知的 f ( x ) f(x) f(x),对于后面的 f ( i ) f(i) f(i)都只会有 f ( i − j ) g ( i ) [ i − j = x ] f(i-j)g(i)[i-j=x] f(i−j)g(i)[i−j=x]的贡献。
所以对于一个当前的区间 l ∼ r , m i d = ⌊ l + r 2 ⌋ l\sim r,mid=\left\lfloor\frac{l+r}{2}\right\rfloor l∼r,mid=⌊2l+r⌋我们已知 f ( l ) ∼ f ( m i d ) f(l)\sim f(mid) f(l)∼f(mid),那么它对于 f ( m i d + 1 ) ∼ f ( r ) f(mid+1)\sim f(r) f(mid+1)∼f(r)的贡献,可以用如下方法快速计算 O ( ( r − l + 1 ) l o g ( r − l + 1 ) ) O((r-l+1)log(r-l+1)) O((r−l+1)log(r−l+1)):
我们令 A ( i ) = f ( i + l ) { i ∈ [ 0 , m i d − l ] } A(i)=f(i+l)\{i\in[0,mid-l]\} A(i)=f(i+l){i∈[0,mid−l]},再令 B ( i ) = g ( i ) { i ∈ [ 1 , r − l ] } B(i)=g(i)\{i\in[1,r-l]\} B(i)=g(i){i∈[1,r−l]},那么我们再令 C ( i ) = A ( i ) ⨂ B ( i ) C(i)=A(i)\bigotimes B(i) C(i)=A(i)⨂B(i)(卷积,相当于 C ( x ) = ∑ A ( i ) × B ( j ) [ i + j = x ] C(x)=\sum A(i)\times B(j)[i+j=x] C(x)=∑A(i)×B(j)[i+j=x]),那么对于 f ( m i d + 1 ) ∼ f ( r ) f(mid+1)\sim f(r) f(mid+1)∼f(r)的其中的 f ( x ) f(x) f(x)贡献就为 C ( x − l − 1 ) C(x-l-1) C(x−l−1),这个就是用 F F T \rm FFT FFT实现了。
那么对于整个区间 [ 1 , n ] [1,n] [1,n],我们分治下去,先计算 [ 1 , n + 1 2 ] [1,\frac{n+1}{2}] [1,2n+1],这时你已经知道前半部分的值,然后 F F T \rm FFT FFT计算贡献,加到后半部分,然后去算 [ n + 1 2 , n ] [\frac{n+1}{2},n] [2n+1,n]的值,这也就是 C D Q \rm CDQ CDQ分治的过程。
总的复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)(总共递归 l o g n logn logn层,每层元素 n n n个, F F T \rm FFT FFT为 O ( n l o g n ) O(nlogn) O(nlogn),所以总复杂度为 O ( 分治 × FFT ) O(\text{分治}\times \text{FFT}) O(分治×FFT))。
Luogu 模板
#include
#include
#include
#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;//r-l+1也可以QAQ
// 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;
}
有没有更加优秀的做法呢?
有,但是只有在取模的意义下,我们只需将式子变形即可。
我们令 F F F为 f f f的生成函数, G G G为 g g g的生成函数。
那么可以得知:
F ( x ) = ∑ i = 0 ∞ f ( i ) x i F(x)=\sum\limits_{i=0}^{\infty}f(i)x^i F(x)=i=0∑∞f(i)xi
G ( x ) = ∑ i = 0 ∞ g ( i ) x i G(x)=\sum\limits_{i=0}^{\infty}g(i)x^i G(x)=i=0∑∞g(i)xi
那么求 F F F和 G G G的卷积就有:
F ( x ) × G ( x ) = ∑ i = 0 ∞ ( ∑ j + k = i f ( j ) x j g ( k ) x k ) = ∑ i = 0 ∞ x i ∑ j + k = i f ( j ) g ( k ) F(x)\times G(x)=\sum_{i=0}^{\infty}\left(\sum_{j+k=i}f(j)x^jg(k)x^k\right)\\ =\sum\limits_{i=0}^{\infty}x^i\sum\limits_{j+k=i}f(j)g(k) F(x)×G(x)=i=0∑∞⎝⎛j+k=i∑f(j)xjg(k)xk⎠⎞=i=0∑∞xij+k=i∑f(j)g(k)
我们发现 x i ∑ j + k = i f ( j ) g ( k ) x^i\sum\limits_{j+k=i}f(j)g(k) xij+k=i∑f(j)g(k)是不是和 F ( x ) F(x) F(x)的第 i i i项很像,所以我们可以知道那个卷积式可以写成 F ( x ) − f ( 0 ) x 0 F(x)-f(0)x^0 F(x)−f(0)x0,也就是 F ( x ) − f ( 0 ) F(x)-f(0) F(x)−f(0)(因为 f ( 0 ) f(0) f(0)不在 x ∈ [ 1 , n − 1 ] x\in[1,n-1] x∈[1,n−1]的范围内,所以无法用 F ( x ) F(x) F(x)表示,所以要减去多出的这一项),那么我们最后求的答案变为下面这个式子:
F ( x ) × G ( x ) = F ( x ) − f ( 0 ) F ( x ) − F ( x ) × G ( x ) = f ( 0 ) F ( x ) × ( 1 − G ( x ) ) = f ( 0 ) 由 于 只 求 前 n 项 , 所 以 可 以 在 模 x n 意 义 下 进 行 F ( x ) ≡ f ( 0 ) 1 − G ( x ) ( m o d    x n ) F ( x ) ≡ ( 1 − G ( x ) ) − 1 ( m o d    x n ) F(x)\times G(x)= F(x)-f(0) \\ F(x)-F(x)\times G(x)=f(0) \\ F(x)\times (1-G(x))=f(0) \\ 由于只求前n项,所以可以在模x^n意义下进行 \\ F(x)\equiv \frac{f(0)}{1-G(x)}(\mod x^n) \\ F(x)\equiv (1-G(x))^{-1}(\mod x^n) F(x)×G(x)=F(x)−f(0)F(x)−F(x)×G(x)=f(0)F(x)×(1−G(x))=f(0)由于只求前n项,所以可以在模xn意义下进行F(x)≡1−G(x)f(0)(modxn)F(x)≡(1−G(x))−1(modxn)
那么我们直接使用多项式求逆即可解决,复杂度 O ( n l o g n ) O(nlogn) O(nlogn)
#include
#include
#include
#include
#define ll long long
using namespace std;
const int M=3e5+10;
const ll Mod=998244353,G=3;
ll Rec[M],A[M],B[M];
ll Groot(ll a,ll b,ll c){
if(Rec[c]) return Rec[c];
ll res=1;
for(;b;b>>=1,a=(a*a)%Mod){
if(b&1) res=(res*a)%Mod;
} return Rec[c]=res;
}
ll Inv(ll a,ll b=Mod-2){
ll res=1;
for(;b;b>>=1,a=(a*a)%Mod){
if(b&1)res=(res*a)%Mod;
} return res;
}
int R[M];
void DFT(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,now;i<=n;i<<=1){
now=i>>1;
ll wn=Groot(G,(Mod-1)/i,i),x,y,w;
for(int j=0;j<n;j+=i){
w=1;
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=Inv(n);
for(int i=0;i<n;i++)a[i]=(a[i]*inv)%Mod;
reverse(a+1,a+n);
}
}
void NTT(ll *a,ll *b,ll *c,int n){
DFT(a,n,1);DFT(b,n,1);
for(int i=0;i<n;i++)c[i]=b[i]*(2ll+Mod-a[i]*b[i]%Mod)%Mod;
DFT(c,n,-1);
}
ll GetInv(ll *a,ll *b,int n){
int lsn=1;
for(;lsn<n;lsn<<=1);n=lsn;
b[0]=Inv(a[0]);
for(int w=1,lg=0,k;w<=n;w<<=1,++lg){
k=w<<1;
for(int i=0;i<k;i++) R[i]=(R[i>>1]>>1)|((i&1)<<lg);
for(int i=0;i<w;i++) A[i]=a[i],B[i]=b[i];
NTT(A,B,b,k);
for(int i=w;i<k;i++) b[i]=0;
}
}
ll g[M],f[M];int n;
int main(){
scanf("%d",&n);
for(int i=1;i<n;i++)scanf("%lld",&g[i]),g[i]=Mod-g[i];
g[0]=1;
GetInv(g,f,n);
for(int i=0;i<n;i++)printf("%lld ",f[i]);
return 0;
}
博主也是才学习,有错误或者疑惑可以提出,大佬带带蒟蒻