zoukankan      html  css  js  c++  java
  • 拆系数FFT学习笔记

    拆系数FFT学习笔记

    拆系数FFT

    当题目中取模的数不是NTT模数的时候,我们无法利用原根来进行快速数论变换,这个时候就要用到毛啸论文里提到的拆系数FFT。

    大致思路

    拆系数FFT实际上是将多项式卷积之后的值具体算出来,普通的FFT由于精度误差较大,当然无法胜任。

    于是可以考虑将一个较大的数拆成两个较小的数,即将(x)拆成(a imes t + b),这样相当于将(x)(t)进制划分,在常数(t)取根号级别的时候,(a,b)的大小也是根号级别的,这样我们再用精度较高的浮点数就可以直接FFT来运算了。

    例如:

    [egin{aligned} x&=a_1t+b_1\ y&=a_2t+b_2\ xy&=(a_1t+b_1)(a_2t + b_2)\ &=a_1a_2t^2 + (a_1b_2+a_2b_1)t+a_2b_2 end{aligned} ]

    (a_1b_1,(a_1b_2+a_2b_1),a_2b_2)对应算出来再各自乘上相应的系数即可。

    这样需要做8次dft,发现中间有两个部分系数相同,对于这两个系数相同的部分我们可以直接点值操作之后再做一次dft即可,这样dft的次数就降到了7次了。

    优化

    DFT

    上面的方法好像常数很大,很多时候需要用到两次DFT合并为一次的方法。

    考虑一个多项式A的DFT本质上求的是(A(omega_n^k)=sum_{j=0}^{n-1}A_{j} imes omega_n^{kj}),现在我们需要同时求出A和B,也就是算出(A(omega_n^k),B(omega_n^k))

    假设我们知道了(F_p[k]=A(omega_n^k)+iB(omega_n^k),F_q[k]=A(omega_n^k)-iB(omega_n^k)),我们就可以通过(F_p[k])(F_q[k])来直接求出(A(omega_n^k),B(omega_n^k))了。

    于是为了合并两次DFT,我们希望(F_p)(F_q)之间能够存在某一种关系,使得求出了某一个之后可以快速求出另外一个。

    于是我们来推导(F_p)(F_q)的关系,为了方便下面的公式的书写,我们约定(X=frac{2pi kj}{n})

    [egin{aligned} F_q[k]&=A(omega_n^k)-iB(omega_n^k)\ &=sum_{j=0}^{n-1}A_jomega_n^{kj}-iB_jomega_n^{kj}\ &=sum_{j=0}^{n-1}(A_j-iB_j)(cos X + isin X)\ &=sum_{j=0}^{n-1}(A_jcos X + B_jsin X)+i(A_jsin X-B_jcos X)\ F_p[n-k]&=A(omega_n^{-k})+iB(omega_n^{-k})\ &=sum_{j=0}^{n-1}A_jomega_n^{-kj}+iB_jomega_n^{-kj}\ &=sum_{j=0}^{n-1}(A_j+iB_j)(cos X -isin X)\ &=sum_{j=0}^{n-1}(A_jcos X + B_jsin X)-i(A_jsin X-B_jcos X)\ end{aligned} ]

    我们发现(F_p[n-k])(F_q[k])在本质上就是共轭的,于是通过(F_p)来计算(F_q)就非常方便了。

    简单分析一下原因,由于(omega_{n}^{n-k}=omega_n^{-k}),而(omega_{n}^{k})(omega_n^{-k})实部和虚部的元素都是相同的,只是符号相反而已,(F_p[k])(F_q[k])的系数刚好也是符号相反,也就导致(F_p[k],F_p[n-k],F_q[k],F_q[n-k])四个项的它们本质的结果只是一些元素的线性组合,这样从(F_p)推出(F_q)也就不是那么难了。

    IDFT

    求出了点值之后我们可以快速将点值乘起来,但是要怎么IDFT回去?

    对于两个点值序列(A,B),我们仍然可以用刚才提到的方法,将每一项合并成(A+iB),将新的多项式IDFT之后得到的系数的实部和虚部就对应了真正(A)(B)了。

    /*=======================================
     * Author : ylsoi
     * Time : 2019.3.14
     * Problem : luogu4245
     * E-mail : ylsoi@foxmail.com
     * ====================================*/
    #include<bits/stdc++.h>
    
    #define REP(i,a,b) for(int i=a,i##_end_=b;i<=i##_end_;++i)
    #define DREP(i,a,b) for(int i=a,i##_end_=b;i>=i##_end_;--i)
    #define debug(x) cout<<#x<<"="<<x<<" "
    #define fi first
    #define se second
    #define mk make_pair
    #define pb push_back
    #define double long double
    typedef long long ll;
    
    using namespace std;
    
    void File(){
        freopen("luogu4245.in","r",stdin);
        freopen("luogu4245.out","w",stdout);
    }
    
    template<typename T>void read(T &_){
        _=0; T fl=1; char _c=getchar();
        for(;!isdigit(_c);_c=getchar())if(_c=='-')fl=-1;
        for(;isdigit(_c);_c=getchar())_=(_<<1)+(_<<3)+(_c^'0');
        _*=fl;
    }
    
    const int maxn=1e5+10;
    const double pi=acos(-1);
    int n,m,mod,a[maxn<<2],b[maxn<<2],c[maxn<<2];
    
    namespace Poly{
        struct cp{
            double x,y;
            cp(double xx=0,double yy=0){x=xx,y=yy;}
            cp conj(){return cp(this->x,-this->y);}
            friend cp operator + (cp a,cp b){return (cp){a.x+b.x,a.y+b.y};}
            friend cp operator - (cp a,cp b){return (cp){a.x-b.x,a.y-b.y};}
            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};}
            friend cp operator * (cp a,double b){return cp(a.x*b,a.y*b);}
            friend cp operator / (cp a,double b){return cp(a.x/b,a.y/b);}
        }om[maxn<<2],aa[maxn<<2],bb[maxn<<2],cc[maxn<<2],dd[maxn<<2];
        int lim,cnt,dn[maxn<<2];
        inline void dft(cp *A,int ty){
            if(ty==-1)reverse(A+1,A+lim);
            REP(i,0,lim-1)if(i<dn[i])swap(A[i],A[dn[i]]);
            for(int len=1;len<lim;len<<=1){
                cp w=om[len<<1];
                for(int L=0;L<lim;L+=len<<1){
                    cp wk=cp(1,0);
                    REP(i,L,L+len-1){
                        cp u=A[i],v=A[i+len]*wk;
                        A[i]=u+v;
                        A[i+len]=u-v;
                        wk=wk*w;
                    }
                }
            }
            if(ty==-1)REP(i,0,lim-1)A[i]=A[i]/lim;
        }
        void mtt(int *A,int *B,int *C,int la,int lb){
            REP(i,0,lim-1){
                dn[i]=dn[i>>1]>>1|((i&1)<<(cnt-1));
                aa[i]= i<=la ? cp(A[i]>>15,A[i]&32767) : cp(0,0);
                bb[i]= i<=lb ? cp(B[i]>>15,B[i]&32767) : cp(0,0);
            }
            dft(aa,1),dft(bb,1);
            REP(i,0,lim-1){
                int j=(lim-i)&(lim-1);
                cp a1=(aa[i]+aa[j].conj())*cp(0.5,0);
                cp a0=(aa[i]-aa[j].conj())*cp(0,-0.5);
                cp b1=(bb[i]+bb[j].conj())*cp(0.5,0);
                cp b0=(bb[i]-bb[j].conj())*cp(0,-0.5);
                cc[i]=a1*b1+a1*b0*cp(0,1);
                dd[i]=a0*b1+a0*b0*cp(0,1);
            }
            dft(cc,-1),dft(dd,-1);
            REP(i,0,lim-1){
                int a1=(ll)(cc[i].x+0.5)%mod;
                int b1=(ll)(cc[i].y+0.5)%mod;
                int c1=(ll)(dd[i].x+0.5)%mod;
                int d1=(ll)(dd[i].y+0.5)%mod;
                C[i]=(((ll)a1<<30)+((ll)(b1+c1)<<15)+(ll)d1)%mod;
            }
        }
    }
    
    int main(){
        File();
    
        read(n),read(m),read(mod);
        REP(i,0,n)read(a[i]);
        REP(i,0,m)read(b[i]);
    
        using namespace Poly;
    
        lim=1,cnt=0;
        while(lim<=n+m)lim<<=1,++cnt;
        if(!cnt)cnt=1;
        REP(i,0,lim-1)dn[i]=dn[i>>1]>>1|((i&1)<<(cnt-1));
        for(int i=lim;i;i>>=1)
            om[i]=cp(cos(2.0*pi/i),sin(2.0*pi/i));
    
        Poly::mtt(a,b,c,n,m);
        REP(i,0,n+m)printf("%d ",(c[i]+mod)%mod);
    
        return 0;
    }
    
    
  • 相关阅读:
    Redis基本数据结构总结之SET、ZSET和HASH
    C# .Net计算函数执行的时间
    千万级规模高性能、高并发的网络架构经验分享
    c#单元测试:使用Moq框架Mock对象
    spring bean的构造器参数
    Java并发编程:ThreadLocal
    redis过期策略
    JAVA线程的生命周期状态
    jdk1.8新特性之Optional
    对Java中interrupt、interrupted和isInterrupted的理解
  • 原文地址:https://www.cnblogs.com/ylsoi/p/10657956.html
Copyright © 2011-2022 走看看