ACM算法总结 FFT与MTT



FFT

全称为快速傅里叶变换,利用复数运算实现DFTIDFT的快速算法,常用于多项式系数的卷积加速。

多项式表示方法一般有两种:系数表示和点值表示。

DFT实际上对应系数到点值的变换,IDFT对应点值到系数的变换。FFT采用分治的思想,结合蝴蝶变换化递归为迭代,通过取特殊点——复数域的单位根 ω n k \omega_n^k (注意因果关系,分治的可行性是基于单位根的选取的)来实现时间复杂度为 O ( n l o g n ) O(nlogn) DFT ,分治决定了 n n 必须是二次幂。而这种特殊点下的点值表示,可以通过取 ω n k \omega_n^{-k} 来实现IDFT(最后要除以n)。(具体证明就不写啦)

代码:

//n一定是2的幂,flag代表是DFT(1)还是IDFT(-1)

void FFT(cp *a,int n,int flag)
{
    for(int i=0;i<n;i++) if(i<r[i]) swap(a[i],a[r[i]]);
    for(int k=1;k<n;k<<=1)
    {
        cp w(1,0),wn(cos(pi/k),sin(pi*flag/k)),x,y;
        for(int i=0;i<n;i+=(k<<1),w=cp(1,0))
            for(int j=0;j<k;j++,w=w*wn)
            {
                x=a[i+j]; y=w*a[i+j+k];
                a[i+j]=x+y; a[i+j+k]=x-y;
            }
    }
    if(flag==-1) for(int i=0;i<n;i++) a[i].a/=n,a[i].b/=n;
}
// l为n的二进制位数,n为不小于项数的最小二次幂

for(int i=0;i<n;i++)
	r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));

顺便来一个复数模板:

struct cp
{
    double a,b;
    cp(double aa=0,double bb=0) {this->a=aa; this->b=bb;}
    cp operator + (const cp x) {return cp(a+x.a,b+x.b);}
    cp operator - (const cp x) {return cp(a-x.a,b-x.b);}
    cp operator * (const cp x) {return cp(a*x.a-b*x.b,b*x.a+a*x.b);}
    cp operator / (const cp x) {return cp((a*x.a+b*x.b)/(x.a*x.a+x.b*x.b),(b*x.a-a*x.b)/(x.a*x.a+x.b*x.b));}
};



MTT

对于特殊的模数,可以用三模数NTT来求卷积,但是这个不普适,考虑任意模数的FFT:

a = k a M + b a ,   b = k b M + b b a=k_aM+b_a, \ b=k_bM+b_b ,那么 a b = k a k b M 2 + ( k a b b + k b b a ) M + b a b b ab=k_ak_bM^2+(k_ab_b+k_bb_a)M+b_ab_b ,于是可以4次DFT加上3次IDFT求解出 a a b b 的系数卷积,总共的复杂度是7次FFT:

int main()
{
    //freopen("input.txt","r",stdin);
    int n=read()+1,m=read()+1,p=read(),l=0,M=(1<<15);
    REP(i,0,n-1) a[i]=read();
    REP(i,0,m-1) b[i]=read();
    for(m+=n-1,n=1;n<m;n<<=1) l++;
    for(int i=0;i<n;i++)
        r[i]=(r[i>>1]>>1)|((i&1)<<(l-1));

    REP(i,0,n-1) ka[i]=cp(a[i]/M,0),ba[i]=cp(a[i]%M,0);
    REP(i,0,n-1) kb[i]=cp(b[i]/M,0),bb[i]=cp(b[i]%M,0);
    FFT(ka,n,1);FFT(kb,n,1);FFT(ba,n,1);FFT(bb,n,1);
    REP(i,0,n-1) A[i]=ka[i]*kb[i],B[i]=ka[i]*bb[i]+kb[i]*ba[i],C[i]=ba[i]*bb[i];
    FFT(A,n,-1);FFT(B,n,-1);FFT(C,n,-1);
    REP(i,0,m-1)
    {
        int x=(LL)floor(A[i].a+0.5)%p;
        int y=(LL)floor(B[i].a+0.5)%p;
        int z=(LL)floor(C[i].a+0.5)%p;
        printf("%d ",(1ll*x*M*M%p+1ll*y*M%p+z)%p);
    }

    return 0;
}

还存在一种优化方法,可以优化到4次FFT,这个以后再补吧。

发布了12 篇原创文章 · 获赞 5 · 访问量 529

猜你喜欢

转载自blog.csdn.net/dragonylee/article/details/102871235
MTT