多项式$fft$,$ntt$,$fwt$初步
最近在学多项式和生成函数。
上课听$lnc$大神讲还是$mengbier$。
作为多项式的前置芝士,$fft,ntt$等是必学的。
在此记录一些关于$fft,ntt,fwt$的知识及例题。。。
FFT:
应用在处理$\sum _{i+j=k} f[i]*g[j]$的卷积上。
看网上大佬的博客,基本入了门吧。
自己的关于原理的一些见解:
多项式有系数表示和点值表示,两种表示方法可以相互转化。
FFT可以在$O(n*longn)$内解决多项式乘法。
具体是,点值表示的方法应用在多项式上是$O(n)$的。
那么如果能在快速时间内将多项式表示成点值,然后$O(n)$处理,再转化成多项式岂不美哉。
于是可一取单位根作为代入多项式的$x$,用分治算法处理即可。
板子:
1 #include<cstdio> 2 #include<iostream> 3 #include<cmath> 4 #define MAXN 362144 5 using namespace std; 6 const double PI=acos(-1); 7 struct Cp{ 8 double x,y; 9 Cp(double xx=0,double yy=0){x=xx;y=yy;return ; } 10 friend Cp operator +(Cp a,Cp b){return Cp(a.x+b.x,a.y+b.y); } 11 friend Cp operator -(Cp a,Cp b){return Cp(a.x-b.x,a.y-b.y); } 12 friend Cp operator *(Cp a,Cp b){return Cp(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x); } 13 }a[MAXN],b[MAXN]; 14 int n,m; 15 int lim=1,l=-1; 16 int ys[MAXN]; 17 inline void FFT(Cp *x,const int &lim,const int &type){ 18 for(int i=0;i<lim;++i)if(i<ys[i])swap(x[i],x[ys[i]]); 19 for(int i=1;i<lim;i<<=1){ 20 Cp tmp(cos(PI/i),sin(PI/i)*type); 21 for(int j=0,I=i<<1;j<lim;j+=I){ 22 Cp w(1,0); 23 for(int k=0;k<i;++k,w=w*tmp){ 24 Cp t1=x[j+k],t2=w*x[k+i+j]; 25 x[j+k]=t1+t2,x[k+i+j]=t1-t2; 26 } 27 } 28 } 29 return ; 30 } 31 int main(){ 32 //freopen("da.in","r",stdin); 33 34 scanf("%d%d",&n,&m); 35 for(int i=0;i<=n;++i)scanf("%lf",&a[i].x); 36 for(int i=0;i<=m;++i)scanf("%lf",&b[i].x); 37 n+=m; 38 for(;lim<=n;lim<<=1,++l); 39 for(int i=0;i<lim;++i)ys[i]=(ys[i>>1]>>1)|((i&1)<<l); 40 FFT(a,lim,1);FFT(b,lim,1); 41 for(int i=0;i<lim;++i)a[i]=a[i]*b[i]; 42 FFT(a,lim,-1); 43 for(int i=0;i<=n;++i)printf("%d ",(int)(a[i].x/lim+0.5)); 44 puts(""); 45 46 return 0; 47 }
NTT:
原理和$FFT$类似。
1 #include<cstdio> 2 #include<iostream> 3 #define MAXN 2197152 4 #define LL long long 5 #define swap(x,y) (x^=y,y^=x,x^=y) 6 using namespace std; 7 const LL mod=998244353; 8 const LL G=3,inv_G=332748118; 9 int n,m; 10 int lim=1,l=-1; 11 int ys[MAXN]; 12 LL a[MAXN],b[MAXN]; 13 LL qpow(LL a,LL b){ 14 LL res=1LL;a%=mod; 15 for(;b;b>>=1,a=a*a%mod)if(b&1)res=res*a%mod; 16 return res%mod; 17 } 18 inline void NTT(LL *x,const int lim,const int type){ 19 for(int i=0;i<lim;++i)if(i<ys[i])swap(x[i],x[ys[i]]); 20 for(int i=1;i<lim;i<<=1){ 21 const int delta=i<<1; 22 const LL tmp=qpow(type==1?G:inv_G,(mod-1)/delta); 23 for(int j=0;j<lim;j+=delta){ 24 LL w=1LL; 25 for(int k=0;k<i;++k,w=w*tmp%mod){ 26 LL t1=x[j+k],t2=w*x[i+j+k]%mod; 27 x[j+k]=(t1+t2)%mod,x[i+j+k]=(t1-t2+mod)%mod; 28 } 29 } 30 } 31 return ; 32 } 33 int main(){ 34 //freopen("da.in","r",stdin); 35 36 scanf("%d%d",&n,&m); 37 for(int i=0;i<=n;++i)scanf("%lld",&a[i]); 38 for(int i=0;i<=m;++i)scanf("%lld",&b[i]); 39 n+=m;for(;lim<n+2;lim<<=1,++l); 40 for(int i=0;i<lim;++i)ys[i]=(ys[i>>1]>>1)|((i&1)<<l); 41 NTT(a,lim,1);NTT(b,lim,1); 42 for(int i=0;i<lim;++i)a[i]=a[i]*b[i]%mod; 43 NTT(a,lim,-1); 44 LL _inv=qpow(lim,mod-2); 45 for(int i=0;i<=n;++i)printf("%lld ",a[i]*_inv%mod); 46 puts(""); 47 48 return 0; 49 }
序列统计:
一道很棒的题,曾经出在达哥的模拟题中。
不过因为当时$M$的范围很小,$O(log(N)*M^2)$水过了。
然而此题必须得学原根了。
原根的两个知识点:
1>求法:拆$\phi(M)$的质因子,从$2$暴枚,满足时当且仅当所有的$x^{\frac{\phi(y)}{d}}!=1$。
2>对于一个在$mod$意义下的原根$x$,$x^1,x^2,x^3.....x^{\phi(mod)}$可以取遍$[1,\phi(mod)]$的数,
其中,有费马小定理$a^{\phi(p)}\equiv 1(mod \ p)$,所以$x^{\phi(mod)}$等同于$x^0$。
对这道题有什么用?
首先有倍增优化的$dp[i]$,表示某长度下,此时结果为$i$的方案数。
转移为$dp[i*j\ mod\ P]=\sum dp[i]*b[j]$,好像每次转移只能$O(n^2)$。
此时考虑应用原根,乘法转化成了加法。
因为每个数都能用原根的次幂表示,所以可以直接用指数来表示一个数。
即为$dp[(i+j)\ mod\ \phi(P)]=\sum dp[i]*b[j]$
变成了我们会优化的形式,每次$NTT$加速即可。
注意$0\in S$,不过$x>=1$,所以直接把$0$舍去即可。
$O(log(N)*M*log(M))$
万径人踪灭:
题目背景保先生以及诗句的含义,啧啧。。。
没有看出来答案为$不连续回文序列个数-连续回文序列个数$。
求后者可以$O(n)$马拉车,也可以二分加$hash$。
1 LL Manacher(){ 2 LL res=0; 3 int j=0; 4 for(int i=1;i<=len;++i)nw[++j]='#',nw[++j]=rn[i]; 5 nw[++j]='#'; 6 int mx=0,id=0; 7 //cout<<j<<endl; 8 for(int i=1;i<=j;++i){ 9 p[i]=(i<mx)?min(p[2*id-i],mx-i):1; 10 while(i-p[i]>=1&&i+p[i]<=j&&nw[i-p[i]]==nw[i+p[i]])++p[i]; 11 if(mx<i+p[i])mx=i+p[i],id=i; 12 int t=p[i]-1; 13 //if(rn[i]=='#')--t; 14 (res+=(t+1)/2)%=mod; 15 //cout<<i<<" "<<p[i]<<" "<<(t+1)/2<<endl; 16 } 17 //cout<<"qwq"<<endl; 18 //scout<<res<<endl; 19 return res; 20 }
前者是可将个数拆成$2^{cnt(a)+cnt(b)+(len\ and\ 1)}$。
求$cnt$,可以让$a$为$1$,$b$为$0$,$FFT$后$f[i]$即为$mid*2$处左右对称$a$的个数。
染色:
首先可以简单推一个$f[i]=\frac{C(M,i)*C(N,i*S)*A(i*S,i*S)}{A(S,S)^i}*(M-i)^{N-S*i}$。
为钦定有$i$个颜色恰好出现了$S$次,我们想要的到$g[i]$为恰好有$i$个颜色出现了$S$次。
考虑有什么联系,发现因为不能保证剩下的颜色是否还会出现$S$次,所以会有重复的。
具体,$j>=i$,$g[j]$会在$f[i]$中算重了$C(j,i)$次,于是有:
$f[i]=\sum_{j=i}^{m}C(j,i)*g[j]$
为二项式反演形式,变成:
$g[i]=\sum_{j=i}^{m}-1^{j-i}C(j,i)*f[j]$
然后把阶乘拆掉就变成卷积的形式辣。