• 丑陋的多项式板子们


    前言

    这篇乘法写的贼烂,真想学懂建议看oi-wiki。
    y1s1,但是感觉后面的多项式其他的基础处理还是讲的可以的。
    (代码轻压,都是跟 WernerYin 学的)
    特别适合蒟蒻yzhx复习

    乘法

    FFT

    主要思路是将“单位根”带入参与运算的两个多项式 (DFT),然后进行点值直接相乘,再反带回来 (IDFT) 得到最后的答案多项式

    所谓“单位根”,是指一些复数平面的单位圆上的n等分点,即:n次单位根
    记为:(omega_n)

    比如:

    而它们有什么性质呢?

    CODE

    //头文件略
    const int _=5e6+5;
    struct Comp{
       db x,y;
       Comp(db _x=0, db _y=0): x(_x), y(_y){};
       friend Comp operator + (Comp a, Comp b) {return Comp(a.x+b.x, a.y+b.y); }
       friend Comp operator - (Comp a, Comp b) {return Comp(a.x-b.x, a.y-b.y); }
       friend Comp operator * (Comp a, Comp b) {return Comp(a.x*b.x-a.y*b.y, a.x*b.y+a.y*b.x); }
    }F[_],G[_];
    int rev[_];
    void change(Comp *f,int l)
    {
       for(re int i=1;i<=l;i++) {rev[i]=rev[i>>1]>>1; if(i&1) rev[i]|=l>>1; }
       for(re int i=1;i<=l;i++) if(i<rev[i]) swap(f[i],f[rev[i]]);
    }
    const db pi=acos(-1);
    void FFT(Comp *f,int l,int on)
    {
       change(f,l);
       for(re int h=2;h<=l;h<<=1) {
          Comp wn(cos(2*pi/h),sin(2*pi*on/h));
          for(re int j=0;j<l;j+=h) {
             Comp w(1,0);
             for(re int k=j;k<j+h/2;k++) {
                Comp u=f[k], v=f[k+h/2]*w;
                f[k]=u+v; f[k+h/2]=u-v; w=w*wn;
             }
          }
       }
       if(on==-1) for(re int i=0;i<l;i++) f[i].x/=l;
    }
    int n,m,ans[_];
    int main()
    {
       n=read(), m=read();
       for(re int i=0;i<=n;i++) F[i].x=read(), F[i].y=0;
       for(re int i=0;i<=m;i++) G[i].x=read(), G[i].y=0;
       n++, m++; int len=1;
       while(len<n*2 || len<m*2) len<<=1;
       FFT(F,len,1), FFT(G,len,1);
       for(re int i=0;i<len;i++) F[i]=F[i]*G[i];
       FFT(F,len,-1);
       for(re int i=0;i<len;i++) ans[i]=int(F[i].x+0.5);
       len=n+m-1;
       for(re int i=0;i<len;i++) cout<<ans[i]<<' ';
    }
    

    NTT

    把单位根替换为指定模数下的原根即可(不懂原根戳这里
    其他操作一样,直接贴代码了

    CODE

    in void NTT(ll *f,int l,int o)
    {
        change(f,l);
        for(re int h=2;h<=l;h<<=1) {
            ll wn=qpow((o==1 ? g: ig), (mod-1)/h);
            for(re int i=0;i<l;i+=h) {
                ll w=1;
                for(re int k=i;k<i+h/2;k++) {
                    ll u=f[k], v=f[k+h/2]*w%mod; w=w*wn%mod;
                    f[k]=(u+v)%mod, f[k+h/2]=(u-v+mod)%mod;
                }
            }
        }
        if(o==-1) {
            ll inv=qpow(l,mod-2);
            for(re int i=0;i<l;i++) f[i]=f[i]*inv%mod;
        }
    }
    

    求逆

    多项式求逆,其实和数字求逆元一样,主要是充当模意义下的除法

    (以下n表多项式的项数,不是次数!!!)
    即:求出(G(x)),满足:(F(x)ast G(x) equiv1 pmod {x^{n/2}})

    令 : (F(x)ast G(x) equiv1 pmod {x^{n/2}})
    (F(x)ast H(x) equiv1 pmod x^{n})
    显然 (H(x)) 的长度相较于 (G(x)) 更长

    所以主要思路是:找出H(x)关于G(x)的表达式,并不断更新

    上面两式相减得(后面的mod n不写了):

    [F(x)ast (G(x)-H(x))equiv 0 ]

    因为F(x)已知非零:

    [(G(x)-H(x))equiv 0 ]

    [(G(x)-H(x))^2equiv 0 ]

    [H^2(x) equiv 2*(Hast G)(x)-G^2(x) ]

    再把 F(x) 乘上去,因为 (F(x)ast H(x)equiv1 pmod x^{n})
    (G(x)在%n意义下不为1)
    有:

    [H(x) equiv 2*G(x)-G^2(x)ast F(x) ]

    然后递归处理即可,递归的边界是 n=1时 G(x)为F(0)的逆元

    CODE

    void SolveInv(ll *f,ll *h,int m)
    {
       if(m==1) { h[0]=qpow(f[0],mod-2); return ;}
       int l=1, mid=m+1>>1; while(l<2*m) l<<=1;
       SolveInv(f,h,mid);
       for(re int i=0;i<m;i++) a[i]=f[i]; memset(a+m,0,(l-m)<<3);
       for(re int i=1;i<l;++i){rev[i]=rev[i>>1]>>1; if(i&1) rev[i]|=l>>1;} 
       //以下三次NTT位数相同,可提前预处理rev
       NTT(a,l,1), NTT(h,l,1);
       for(re int i=0;i<l;++i) h[i]=h[i]*(2-a[i]*h[i]%mod+mod)%mod;
       NTT(h,l,0); memset(h+m,0,(l-m)<<3);
    }
    

    开根

    求G(x),满足:(G^2(x)equiv F(x) pmod{n/2})

    思路:

    主要也是利用和求逆一样的思想

    令 : $$G^2(x)equiv F(x) pmod{(n/2)/2}$$
    $$H^2(x)equiv F(x) pmod{n/2}$$
    两式相减化简后再将(H^2(x)equiv F(x)) 带入,可得:

    [H(x)equiv (1/2)ast(G(x)+F(x)/G(x)) ]

    注意到中间有个加号,所以只需要NTT时处理F(x)与G(x)的逆即可

    CODE

    #define mem(x) memset(x+m,0,(l-m)<<3);
    in void Sqrt(ll *f,ll *h,int m)
    {
       if(m==1) {h[0]=1; return ;}
       int l=1; while(l<(m<<1)) l<<=1;; Sqrt(f,h,m+1>>1);
       memset(b,0,l<<3);Inv(h,b,m);
       for(re int i=1;i<l;++i) rev[i]=rev[i>>1]>>1|(i&1?l>>1:0);
       for(re int i=0;i<m;i++) c[i]=f[i]; mem(c);
       NTT(c,l), NTT(b,l); for(re int i=0;i<l;++i) b[i]=b[i]*c[i]%mod;
       NTT(b,l,0); for(re int i=0;i<l;++i) h[i]=(h[i]+b[i])%mod*inv2%mod;
       mem(h);
    }
    
    嗯,就这样了...
  • 相关阅读:
    CF351A Jeff and Rounding 思维
    CF352B Jeff and Periods 模拟
    CF352A Jeff and Digits
    小B的询问 莫队分块
    小凯的疑惑 数学
    BestCoder Round #80 待填坑
    [SDOI2009]HH的项链 树状数组 BZOJ 1878
    Blocks poj 区间dp
    [USACO5.4]奶牛的电信Telecowmunication 最小割
    数位dp
  • 原文地址:https://www.cnblogs.com/yzhx/p/14495451.html
Copyright © 2020-2023  润新知