多项式$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 }
View Code

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 }
View Code

序列统计:

  一道很棒的题,曾经出在达哥的模拟题中。

  不过因为当时$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$加速即可。

  注意$0in 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 }
manacher

  前者是可将个数拆成$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]$

  然后把阶乘拆掉就变成卷积的形式辣。


  

  

  

原文地址:https://www.cnblogs.com/2018hzoicyf/p/12028151.html