zoukankan      html  css  js  c++  java
  • FFT&NTT&多项式全家桶

    FFT

    前置芝士

    多项式表示

    系数表示法

    (A(x)=sum_{i=0}^{n-1} a_i*x^i) 表示一个 (n-1) 次的多项式。 计算多项式乘法复杂度为 (O(n^2))

    点值表示法

    (n) 个互不相同的 (x_i) 带入得到 (n) 个$ y_i=sum_{j=0}^{n-1} a_j*{x_i}^j$

    (n) 个点((x_1,y_1)...(x_n,y_n))表示一个 (n-1) 次多项式

    复数

    定义

    模长: (sqrt{x^2+y^2})

    幅角:从 (x) 轴正半轴到向量的转角的有向角

    运算原则

    • 加法

      平行四边形法则

    • 乘法:

      几何:模长相乘,幅角相加

      代数: ((a+bi)*(c+di)=ac-bd+(bc+ad)i)

    单位根

    定义

    (n) 为 2 的正整数幂。

    复平面上的单位圆,以原点为起点,圆的 (n) 等分点为终点,做(n) 个向量。

    设幅角为正且最小的向量对应的复数为 (omega_n) ,称为 (n) 次单位根。

    其余 (n-1) 个复数为 ({omega_n}^2,{omega_n}^3...{omega_n}^n)

    另外,在代数中,若 (z^n=1),我们把 (z) 称为 (n) 次单位根。

    单位根的性质
    • ({omega_n}^k=cos {k {frac {2pi} n}}+i sin {k {frac {2pi} n}}) 欧拉公式

    • ({omega_{2n}}^{2k}={omega_n}^k) 用欧拉公式证一下就行。

    • ({omega_n}^{k+frac n 2}=-{omega_n}^k) 可以用欧拉公式证明 ({omega_n}^{frac n 2}=-1)

    • ({omega_n}^0={omega_n}^n=1) 几何意义显然。

    快速傅里叶变换

    用点值表示法直接狂做,仍然是 (O(n^2)) 的,没有意义。

    设多项式 (A(x)) 系数为 ((a_0,a_1,a_2,...a_{n-1}))

    [A(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1} ]

    将其下标奇偶分类

    [A(x)=(a_0+a_2x^2+...+a_{n-2}x^{n-2})+ (a_1x+a_3x^3+...+a_{n-1}x^{n-1}) ]

    [A_1(x)=a_0+a_2x+...+a_{n-2}x^{frac n 2-1}\ A_2(x)=a_1+a_3x+...+a_{n-1}x^{frac n 2-1} ]

    [A(x)=A1(x^2)+xA_2(x^2) ]

    利用单位根的性质

    考虑用单位根作为 (x)

    ({omega_n}^k(k<frac n 2)) 带入得

    [A({omega_n}^k)=A1({omega_n}^{2k})+{omega_n}^kA_2({omega_n}^{2k})\ =A_1({omega_{frac n 2}}^{k})+{omega_n}^kA_2({omega_{frac n 2}}^{k}) ]

    ({omega_n}^{k+frac n 2}) 带入得

    [A(omega_n^{k+frac{n}{2}})=A_1(omega_n^{2k+n})+omega_n^{k+frac{n}{2}}(omega_n^{2k+n})\ =A_1(omega_n^{2k})+omega_n^{k+frac{n}{2}}(omega_n^{2k})\ =A_1({omega_{frac n 2}}^{k})-{omega_n}^kA_2({omega_{frac n 2}}^{k}) ]

    这两个柿子只有一个符号不同。

    前一个(kin[0,frac n 2)) ,后一个 ((k+frac n 2) in [frac n 2,n))

    计算前一个时就可以同时计算后一个。

    时间复杂度 (O(nlog n))

    快速傅里叶逆变换

    用于将点值表示法转化为系数表示法。

    ((a_0,a_1,...,a_n)) 的傅里叶变换(点值表示法)为 ((y_0,y_1,...,y_n-1))

    多项式 (B(x)=sum_{i=0}^{n-1} y_ix^i) 的点值表示法是 (({omega_{n}}^{-i},c_i))

    ((c_0,c_1,...c_{n-1})) 满足

    [egin{aligned} c_k&=sum_{i=0}^{n-1} y_i({omega_n}^{-k})^i\ &=sum_{i=0}^{n-1} (sum_{j=0}^{n-1} a_j{x_i}^j)({omega_n}^{-k})^i\ &=sum_{i=0}^{n-1} (sum_{j=0}^{n-1} a_j{{(omega_n}^i)}^j)({omega_n}^{-k})^i\ &=sum_{i=0}^{n-1} (sum_{j=0}^{n-1} a_j{{(omega_n}^i)}^j({omega_n}^{-k})^i)\ &=sum_{i=0}^{n-1} (sum_{j=0}^{n-1} a_j{{(omega_n}^j)}^i({omega_n}^{-k})^i)\ &=sum_{j=0}^{n-1} a_j sum_{i=0}^{n-1} {{(omega_n}^{j-k})}^i\ end{aligned} ]

    (S(x)=sum_{i=0}^{n-1} x_i)

    [S(omega_n^k)=sum_{i=0}^{n-1} {({omega_n}^k)}^i ]

    等比数列求和之后

    • (k=0) , (s=n)
    • (k eq 0) , (s=0)

    考虑之前的柿子。

    [c_k=sum_{j=0}^{n-1} a_j sum_{i=0}^{n-1} {{(omega_n}^{j-k})}^i\ ]

    • (j=k) 时,为 (n)

    • (j eq k) 时,为 (0)

    所以 (c_k=na_k)

    [a_k=frac {c_k} n ]

    总结

    代码实现

    • 尽量手写虚数

    • 递归版

      #include<bits/stdc++.h>
      using namespace std;
      const int N=1e7+10;
      int n,m;
      double pi=acos(-1);
      struct cplx{
          double x,y;
          cplx(double xx=0,double yy=0){x=xx;y=yy;}
      }f[N],g[N];
      cplx operator + (cplx a,cplx b){ return cplx(a.x+b.x,a.y+b.y);}
      cplx operator - (cplx a,cplx b) {return cplx(a.x-b.x,a.y-b.y);}
      cplx operator * (cplx a,cplx b) {return cplx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x);}
      void fft(int lim,cplx *a,int tp){
          if(lim==1) return;
          cplx a1[(lim>>1)+5],a2[(lim>>1)+5];
          for(int i=0;i<=lim;i+=2) a1[i>>1]=a[i],a2[i>>1]=a[i+1];
          fft(lim>>1,a1,tp);fft(lim>>1,a2,tp);
          cplx tmp=cplx(cos(2*pi/lim),tp*sin(2*pi/lim)),bg=cplx(1,0);
          for(int i=0;i<(lim>>1);i++,bg=bg*tmp){
              a[i]=a1[i]+bg*a2[i];
              a[i+(lim>>1)]=a1[i]-bg*a2[i];
          }
          return;
      }
      int main(){
          scanf("%d%d",&n,&m);
          for(int i=0;i<=n;i++) scanf("%lf",&f[i].x);
          for(int i=0;i<=m;i++) scanf("%lf",&g[i].x);
          int limit=1;while(limit<=n+m) limit<<=1;
          fft(limit,f,1);fft(limit,g,1);
          for(int i=0;i<=limit;i++) f[i]=f[i]*g[i];
          fft(limit,f,-1);
          for(int i=0;i<=m+n;i++) printf("%d ",(int)(f[i].x/limit+0.5) );
          return 0;
      }
      
    • 迭代实现

      需要求的序列实际是原序列下标的二进制反转。

      因此对序列按照下标的奇偶性分类的过程其实是没有必要的。

      #include<bits/stdc++.h>
      using namespace std;
      typedef long long ll;
      const int N=4e6+10;
      double pi=acos(-1.0);
      int n,m,id[N];
      struct cplx{
          double x,y;
          cplx(double xx=0,double yy=0){ x=xx; y=yy; }
      }f[N],g[N];
      cplx operator + (cplx a,cplx b) { return cplx(a.x+b.x,a.y+b.y); }
      cplx operator - (cplx a,cplx b) { return cplx(a.x-b.x,a.y-b.y); }
      cplx operator * (cplx a,cplx b) { return cplx(a.x*b.x-a.y*b.y,a.x*b.y+a.y*b.x); }
      void FFT(int lim,cplx *a,int tp){
          for(int i=0;i<lim;i++) if(id[i]>i) swap(a[i],a[id[i]]);
          for(int mid=1;mid<lim;mid<<=1){
              cplx wn(cos(pi/mid),tp*sin(pi/mid));
              for(int j=0,len=mid<<1;j<lim;j+=len){
                  cplx w(1,0);
                  for(int k=0;k<mid;k++,w=w*wn){
                      cplx x=a[j+k],y=w*a[j+k+mid];
                      a[j+k]=x+y; a[j+k+mid]=x-y;
                  }
              }
          }
          return;
      }
      int main(){
          scanf("%d%d",&n,&m);
          for(int i=0;i<=n;i++) scanf("%lf",&f[i].x);
          for(int i=0;i<=m;i++) scanf("%lf",&g[i].x);
          int lim=1,len=0; while(lim<=n+m) lim<<=1,len++;
          for(int i=0;i<lim;i++) id[i]=(id[i>>1]>>1)|((i&1)<<(len-1));
          FFT(lim,f,1); FFT(lim,g,1);
          for(int i=0;i<=lim;i++) f[i]=f[i]*g[i];
          FFT(lim,f,-1);
          for(int i=0;i<=n+m;i++)
              printf("%d ",(int)(f[i].x/lim+0.5));
          puts("");
          return 0;
      }
      

      如果你是一个背板子带师,你需要记住:

      • ({omega_n}^k=cos {k {frac {2pi} n}}+i sin {k {frac {2pi} n}})

      • (A({omega_n}^k)=A_1({omega_{frac n 2}}^{k})+{omega_n}^kA_2({omega_{frac n 2}}^{k}))

      • (A({omega_n}^{k+frac n 2})=A_1({omega_{frac n 2}}^{k})-{omega_n}^kA_2({omega_{frac n 2}}^{k}))

      • (a_k=frac {c_k} n)

    NTT

    可以解决FFT精度过低的问题。

    原根

    定义

    考虑一个质数 (p) ,定义其原根 (g) 为使得 (g^i(0le ile p-2)) 互不相同的数。

    为了满足单位根的四条性质,(n) 要是 (p-1) 的因数才能满足条件,(n) 是 2 的幂,所以 (p) 是形如 (q·2^k+1) 的质数。

    http://blog.miskcoo.com/2014/07/fft-prime-table

    常见模数998244353,1004535809,469762049998244353,1004535809,469762049,这些原根都是3

    性质

    FFT依赖了单位根的四条性质,原根都满足。

    ({t_n}^nequiv 1 mod p)(t_nequiv g^q mod p),等价与 (omega_n)

    • ({omega_n}^t) 互不相同,保证点值表示的合法。显然满足。

    • ({omega_{2n}}^{2k}={omega_n}^k) 用于分治。

      ({t_{2n}}=g^{frac q 2}(p=frac q 2 imes 2n+1))

      ({t_{2n}}^{2k}=g^{2kfrac q 2}=g^{kq}=(t_n)^k)

    • ({omega_{n}}^{k+frac n 2}=-{omega_n}^k) ,用于分治

      由费马小定理 ({t_n}^n=g^{nq}=g^{p-1}equiv 1 mod p)

      ({t_n^{frac n 2}}=1/-1)

      ({t_n^{frac n 2}} eq {t_n^{0}})({t_n^{frac n 2}}=-1)

      可推出 ({t_{n}}^{k+frac n 2}={t_{n}}^{k} imes {t_n^{frac n 2}}=-{t_n}^k)

    • S(x) 在 (x=0) 时为 (n) 否则为 (0)

      等比数列一番再用性质三的结论。

    求任意一个质数的原根

    可以一一枚举 (g) 并检验。

    结论:对于一个数 (g),最小的满足(g^kequiv 1mod p) 的正整数 (k) 一定是 (p-1) 的约数。

    Proof

    如果最小的 (k) 不是 (p-1) 的约数,

    找到 (x) 满足 (xk>p-1>(x-1)k)

    [g^{xk}equiv g^{p-1}equiv g^{xk-(p-1)}mod p \xk-(p-1)<k ]

    矛盾

    检验时,只要枚举 (p-1) 的所有约数 (q) ,检验 (g^q eq 1 mod p) 即可。

    代码实现

    求原根

    我没搞懂我是傻逼。

    inline long long pow(const long long x, const long long n, const long long p) {
        long long ans = 1;
        for (long long num = x, tmp = n; tmp; tmp >>= 1, num = num * num % p) if (tmp & 1) ans = ans * num % p;
        return ans;
    }
    
    inline long long root(const long long p) {
        for (int i = 2; i <= p; i++) {
            int x = p - 1;
            bool flag = false;
            for (int k = 2; k * k <= p - 1; k++) if (x % k == 0) {
                if (pow(i, (p - 1) / k, p) == 1) {
                    flag = true;
                    break;
                }
                while (x % k == 0) x /= k;
            }
    
            if (!flag && (x == 1 || pow(i, (p - 1) / x, p) != 1)) {
                return i;
            }
        }
        throw;
    }
    

    NTT

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    const int N=4e6+10,mod=998244353;
    int n,m,id[N],A[N],B[N],g=3;
    int power(int a,int b){
        int ret=1;
        while(b){
            if(b&1) ret=1ll*ret*a%mod;
            b>>=1;a=1ll*a*a%mod;
        }
        return ret;
    }
    void NTT(int lim,int *a,int tp){
        for(int i=0;i<lim;i++) if(id[i]>i) swap(a[i],a[id[i]]);
        for(int mid=1;mid<lim;mid<<=1){
            int wn=power(g,(mod-1)/(mid<<1));
            if(tp==-1) wn=power(wn,mod-2);
            for(int j=0,len=mid<<1;j<lim;j+=len){
                int w=1;
                for(int k=0;k<mid;k++,w=1ll*w*wn%mod){
                    int x=a[j+k],y=1ll*w*a[j+k+mid]%mod;
                    a[j+k]=(x+y)%mod; a[j+k+mid]=(x-y)%mod;
                }
            }
        }
        return;
    }
    int main(){
        scanf("%d%d",&n,&m);
        for(int i=0;i<=n;i++) scanf("%d",&A[i]);
        for(int i=0;i<=m;i++) scanf("%d",&B[i]);
        int lim=1,len=0; while(lim<=n+m) lim<<=1,len++;
        for(int i=0;i<lim;i++) id[i]=(id[i>>1]>>1)|((i&1)<<(len-1));
        NTT(lim,A,1); NTT(lim,B,1);
        for(int i=0;i<lim;i++) A[i]=1ll*A[i]*B[i]%mod;
        NTT(lim,A,-1);
        int inv=power(lim,mod-2);
        for(int i=0;i<=n+m;i++)
            printf("%d ",(int)((1ll*A[i]*inv%mod+mod)%mod));
        puts("");
        return 0;
    }
    

    多项式全家桶

    泰勒展开

    泰勒有一个看着不顺眼的函数,比如说是 (sin(x))

    泰勒要制造一个图像和它很相似的多项式函数

    泰勒会希望它的 (n) 阶导都相等

    最后结果一定是

    [g(x)=a_0+a_1x+a_2x^2+...a_nx^n ]

    这样的,也就是要求 (a)

    保证初始点相同,(g(0)=f(0)=a_0)

    (f/g) 求完 (n) 阶导的时候,只有最后一项非0,是 (n!a_n)

    初始点也不一定是0,所以泰勒展开的结果是

    [G(x)=F(x_0)+dfrac{F^{(1)}(x_0)}{1!}(x-x_0)+dfrac{F^{(2)}(x_0)}{2!}(x-x_0)^2+cdots ]

    差不多这个意思,还是看 zzc 的或者 原版为好。

    牛顿迭代

    以多项式求逆为例。

    已知 (H(x)) ,且 (F(x)*H(x)equiv 1 mod {x^{lceil frac n 2 ceil}})

    已知一个函数满足 (G(F(x))equiv 0mod {x^n}) ,已知的!已知的!已知的!!!

    (G(F(x)))(H(x)) 泰勒展开

    [G(F(x))=G(H(x))+dfrac{G'(H(x))}{1!}(F(x)-H(x))+dfrac{G''(H(x))}{2!}(F(x)-H(x))^2+cdots\ ]

    注意,这不是在拟合什么,G和G本来就一样,只是为了制造 (F(x))(H(x)) 之间的关系柿。。。

    从第三项开始,由于 (F(x),H(x)) 的后 (frac n 2) 项相同,所以都是0

    [G(F(x))=G(H(x))+{G'(H(x))}(F(x)-H(x)) ]

    所以

    [G(H(x))+{G'(H(x))}(F(x)-H(x)) equiv 0 mod {x^n}\ F(x)=H(x)-frac {G(H(x))} {G'(H(x))} ]

    [F(x)=F_0(x)-frac {G(F_0(x))} {G'(F_0(x))} ]

    每次如此,让它迅速逼近准确值。

    本质上,迭代一次精度就可以翻倍。从 (mod x^{frac n 2})(mod x^n)

    然而没人说证明。

    多项式求逆

    目标

    求满足 (F(x)G(x)equiv 1mod x^n)(G) ,系数对998244353取模。

    求解

    考虑倍增。

    如果多项式 (F) 只有一项,那么显然 (G_0)(F_0) 的逆元。若有 (n) 项,递归求解。

    注意,以下 (frac n 2) 均指 (lceil frac n 2 ceil)(') 不是求导后的结果。

    假设已知

    [F(x)G'(x)equiv 1 mod x^{ frac n 2 } ]

    需要求

    [F(x)G(x)equiv 1 mod x^{ n } ]

    显然

    [G(x)-G'(x)equiv 0 mod x^{frac n 2} ]

    两边同时平方

    [G^2(x)+{G'}^2(x)-2G(x)G'(x)equiv 0 mod x^{n} ]

    两边同乘 (F(x))

    [F(x)(G^2(x)+{G'}^2(x)-2G(x)G'(x))equiv 0 mod x^{n} ]

    又因为 (F(x)G(x)equiv 1 mod x^{ n })

    所以

    [G(x)+F(x){G'}^2(x)-2G'(x)equiv 0 mod x^{n} ]

    移项

    [G(x) equiv 2G'(x)-F(x){G'}^2(x) mod x^{n} ]

    只需初始 (G(0)=F(0)^{-1}) 即可倍增。

    时间复杂度:(T(n)=T(frac n 2)+O(nlog n)=O(nlog n))

    关于reverse:思考一下同余的事

    关于预处理:??

    多项式对数函数

    目标

    求一个 (B(x)equiv ln A(x)mod {x^n})

    求解

    [C(x)=ln x\ {ln A(x)}'=C'(A(x))=C'(A(x))A'(x)=frac 1 {A(x)}·A'(x)=frac {A'(x)} {A(x)}\ B'(x)=ln A(x)\ B'(x)=frac {A'(x)} {A(x)}\ ]

    因此只要导一下+求逆+积分回来(?代码中似乎并不是积分)

    多项式指数函数(exp)

    目标

    (B(x)) 满足 (B(x)equiv {e^{A(x)}}mod {x^n}) 保证 (A(0)=0)

    求解

    [ln (B(x))=A(x)\ ]

    (C(B(x))=ln (B(x))-A(x)) ,那么 (C'(B(x))=frac {1} {B(x)})

    (A(x)) 可以当做常数量消掉。(x) 的改变,不会引起系数的改变。)

    对其牛顿迭代。

    [B(x)=B_0(x)-frac {C(B_0(x))} {C'(B_0(x))}\ B(x)=B_0(x)-frac {ln (B_0(x))-A(x)} {frac 1 {B_0(x)}}\ B(x)=B_0(x)-{(ln (B_0(x))-A(x)} ){B_0(x)}\ B(x)={(1-ln (B_0(x))+A(x)} ){B_0(x)}\ ]

    这里的 (B_0(x)) 指的是 (modfrac n 2) 下的答案。依然递归求解即可。

    多项式开根

    目标

    (B(x)) 使得 (B^2(x)equiv A(x)mod {x^n})

    若有多解,请取零系数较小的作为答案。

    求解

    (G(B(x))=B(x)^2-A(x))

    (G'(B(x))=2B(x))

    对其牛顿迭代

    [egin{aligned} B(x)&=B_0(x)-frac {G(B_0{x})} {G'(B_0(x))}\ &=B_0(x)-frac {G(B_0(x))} {2B_0(x)}\ &=frac {{B_0}^2(x)+A(x)} {2B_0(x)}\ &=frac {B_0(x)} 2+frac {A(x)} {2B_0(x)} end{aligned} ]

    改一改exp就得了

    多项式快速幂

    目标

    求一个 (B(x)equiv A^k(x)mod {x^n})

    解法

    两边同时取 (ln)

    [ln(B(x))=ln (A^k(x))\ ln(B(x))=kln (A(x)) ]

    再把 (ln(B(x))) exp回去即可。

    多项式除法

    目标

    给定一个 (n) 次多项式 (F(x)) 和一个 (m) 次多项式 G(x) ,请求出多项式 (Q(x)) , (R(x)) ,满足以下条件:

    • (Q(x)) 次数为 (n-m),$$R(x)$$ 次数小于 (m)
    • (F(x) = Q(x) * G(x) + R(x))

    所有的运算在模 998244353 意义下进行。

    求解

    定义 (F_r(x)) 为把 (F(x)) 系数翻转后的函数。

    [F(x)=sum_{i=0}^n a_ix^i ]

    [F_r(x)=sum_{i=0}^n a_{n-i}x^i\ F_r(x)=x^nF(x^{-1}) ]

    要满足

    [F(x) = Q(x)G(x) + R(x)\ F(x^{-1}) = Q(x^{-1})G(x^{-1}) + R(x^{-1})\ x^nF(x^{-1}) = x^m Q(x^{-1}) x^{n-m}G(x^{-1}) +x^n R(x^{-1})\ F_r(x)=G_r(x)Q_r(x)+x^nR(x^{-1})\ ]

    两边同时模 (x^{n-m+1})

    [F_r(x)equiv G_r(x)Q_r(x) mod x^{n-m+1}\ Q_r(x)equiv F_r(x){G_r}^{-1}(x) mod x^{n-m+1} ]

    可以计算出 (Q(x)) ,容易算出 (R(x))

    const int N=4e6+10,mod=998244353;
    inline int Add(int x,int y){ x+=y; if(x>=mod) x-=mod; return x; }
    inline int Sub(int x,int y){ x-=y; if(x<0) x+=mod; return x; }
    inline int mul(int x,int y){ return 1ll*x*y%mod; }
    inline int read(){ char ch; ch=getchar(); int num=0,fff=1; while(ch<'0'||ch>'9'){ if(ch=='-') fff=-1; ch=getchar(); } while(ch>='0'&&ch<='9') num=(num<<3)+(num<<1)+ch-'0',ch=getchar(); return num*fff; }
    inline void write(int x){ if(x<0) putchar('-'),x=-x; if(x>=10) write(x/10); putchar(x%10+'0'); return; }
    inline int power(int a,int b){ int ret=1; while(b){ if(b&1) ret=mul(ret,a); b>>=1;a=mul(a,a); } return ret; }
    inline int inv(int x){ return power(x,mod-2); }
    namespace poly{
        int len,lim,id[N],pre[N],G=3,invG=332748118;
        inline void getlim(int x){ len=0,lim=1; while(lim<x) lim<<=1,len++; }
        inline void initid(){ for(int i=0;i<lim;i++) id[i]=(id[i>>1]>>1)|((i&1)<<(len-1)); }
        void initpre(){
            for(int i=1;i<lim;i<<=1){
                int w=power(3,(mod-1)/(i<<1)); pre[i]=1;
                for(int j=1;j<i;j++) pre[i+j]=mul(pre[i+j-1],w);
            } 
        }
        void NTT(int *a,bool tp){
            for(int i=0;i<lim;i++) if(i<id[i]) swap(a[i],a[id[i]]);
            for(int mid=1,cnt=0;mid<lim;mid<<=1,cnt++)
                for(int j=0,len=mid<<1;j<lim;j+=len)
                    for(int k=0;k<mid;k++){
                        int x=a[j+k],y=mul(pre[mid+k],a[j+k+mid]);
                        a[j+k]=Add(x,y); a[j+k+mid]=Sub(x,y);
                    }
            if(tp) return;
            int invlim=inv(lim);
            for(int i=0;i<lim;i++) a[i]=mul(a[i],invlim);
            reverse(a+1,a+lim);
            return;
        }
        inline void getmul(int n,int m,int *a,int *b){
            getlim(n+m); initid();
            NTT(a,1); NTT(b,1);
            for(int i=0;i<lim;i++) a[i]=mul(a[i],b[i]);
            NTT(a,0);
            return;
        }
        inline void getinv(int n,int *a,int *b){
            static int tmp[N];
            getlim(n<<1);
            int m=lim;
            for(int i=0;i<lim;i++) b[i]=0;
            b[0]=inv(a[0]);
            for(int step=2;step<m;step<<=1){
                getlim(step<<1); 
                for(int i=0;i<lim;i++) tmp[i]=(i<step)?a[i]:0;
                initid();
                NTT(tmp,1); NTT(b,1);
                for(int i=0;i<lim;i++) b[i]=mul(Sub(2,mul(b[i],tmp[i])),b[i]);
                NTT(b,0);
                for(int i=step;i<lim;i++) b[i]=0;
            }
            return;
        }
        inline void getdao(int n,int *a,int *b){ b[n-1]=0; for(int i=1;i<n;i++) b[i-1]=mul(i,a[i]); }
        inline void getjifen(int n,int *a,int *b){ b[0]=0; for(int i=1;i<n;i++) b[i]=mul(inv(i),a[i-1]); }
        inline void getln(int n,int *a,int *b){
            static int A[N],B[N];
            for(int i=0;i<lim;i++) A[i]=B[i]=b[i]=0; 
            getlim(n<<1); getdao(n,a,A);
            getinv(n,a,B); getmul(n,n,A,B);
            getjifen(n,A,b);
        }
        void getexp(int n,int *a,int *b){
            a[0]++;
            static int tmp[N];
            getlim(n<<1);
            int m=lim;
            for(int i=0;i<m;i++) tmp[i]=0;
            b[0]=1; 
            for(int step=2;step<m;step<<=1){
                getlim(step<<1); getln(step,b,tmp);
                for(int i=0;i<step;i++) tmp[i]=Sub(a[i],tmp[i]);
                getmul(step,step,b,tmp);
                for(int i=step;i<lim;i++) b[i]=tmp[i]=0;
            }
            a[0]--;
        }
        void getsqrt(int n,int *a,int *b){
            static int tmp[N],tmp2[N];
            int m=lim;
            for(int i=0;i<m;i++) tmp[i]=tmp2[N]=0;
            b[0]=1; 
            for(int step=2;step<m;step<<=1){
                getlim(step<<1); getinv(step,b,tmp);
                for(int i=0;i<step;i++) tmp2[i]=a[i];
                getmul(step,step,tmp,tmp2);
                int inv2=inv(2); for(int i=0;i<step;i++) b[i]=mul(Add(b[i],tmp[i]),inv2);
                for(int i=step;i<lim;i++) b[i]=tmp[i]=tmp2[i]=0;
            }
            return;
        }
        void getpower(int n,int k,int *a,int *b){
            static int tmp[N];
            getln(n,a,tmp);
            for(int i=0;i<n;++i) tmp[i]=mul(tmp[i],k);
            getexp(n,tmp,b);
        }
        void getdiv(int n,int m,int *a,int *b,int *c,int *d){
            static int ar[N],br[N],tmp[N];
            for(int i=0;i<n;i++) ar[i]=a[n-1-i];
            for(int i=0;i<m;i++) br[i]=b[m-1-i],tmp[i]=b[i];
            for(int i=n-m+1;i<lim;i++) ar[i]=0,br[i]=0;
            getinv(n-m+1,br,c);
            
            getmul(n,n-m+1,c,ar);
            reverse(c,c+n-m+1);
            for(int i=n-m+1;i<lim;i++) c[i]=0;
            for(int i=0;i<n-m+1;i++) d[i]=c[i];
            getmul(n-m+1,m,d,tmp);
            for(int i=0;i<m-1;i++) d[i]=Sub(a[i],d[i]);
            return;
        }
    }
    
    qaqaq
  • 相关阅读:
    MongoDB + Spark: 完整的大数据解决方案
    07对象字面量
    05JavaScript中数组的使用
    04JavaScript中函数也是对象
    03JavaScript中的函数预解析
    02通过arguments实现方法重载
    01函数重名问题
    mxGraph 学习笔记 --mxGraph常用功能代码
    mxGraph学习笔记--设置节点鼠标事件
    mxGraph 学习笔记 --右键菜单
  • 原文地址:https://www.cnblogs.com/zdsrs060330/p/14315454.html
Copyright © 2011-2022 走看看