FFT简明指南

简介

FFT是用来计算多项式卷积的东西。

多项式卷积: C = A ∗ B C=A*B C=AB ,即 c k = ∑ i + j = k a i ∗ b j c_k=\sum_{i+j=k} a_i*b_j ck=i+j=kaibj 。(假设下标范围 0 − n 0-n 0n

直接按照定义做是 O ( n 2 ) O(n^2) O(n2) 的,但是FFT可以做到 n l o g ( n ) nlog(n) nlog(n)

一些奇奇怪怪的东西(定义)

考虑一个 n n n 次多项式,取 n n n 个不同的 x x x 代入会得到 n n n y y y ,然后发现由于我们可以解方程,因此只要不是无解、多解,我们可以说,一个 n n n 次多项式和一个(有 n n n 个点的集合一一对应)。

对于多项式 A = ∑ i = 0 n − 1 a i ∗ x i A=\sum_{i=0}^{n-1}a_i*x^i A=i=0n1aixi ,定义其在 x = x 0 x=x_0 x=x0 处的点值为 A ( x 0 ) = ∑ i = 0 n − 1 a i ∗ x 0 i A(x_0)=\sum_{i=0}^{n-1}a_i*x_0^i A(x0)=i=0n1aix0i

多项式系数表示:对于 A = ∑ i = 0 n − 1 a i ∗ x i A=\sum_{i=0}^{n-1} a_i*x^i A=i=0n1aixi ,系数表示为 A = { a 0 , a 1 , . . . , a n − 1 } A=\{a_0,a_1,...,a_{n-1}\} A={a0,a1,...,an1}

多项式点值表示: A = { ( x 0 , y 0 ) , ( x 1 , y 1 ) , . . . , ( x n − 1 , y n − 1 ) } A=\{(x_0,y_0),(x_1,y_1),...,(x_{n-1},y_{n-1})\} A={(x0,y0),(x1,y1),...,(xn1,yn1)} 。如果 x x x 的取值确定的情况下,显然也可以写成 A = { y 0 , y 1 , . . . , y n − 1 } A=\{y_0,y_1,...,y_{n-1}\} A={y0,y1,...,yn1} 。这样的话,多项式的系数、点值表示都可以写成一个向量vector的形式了。

点值表示中,若A与B x x x 的取值相同,它们相乘只需要把 x x x 对应的 y y y 分别相乘即可。

n次单位根(其实n次单位根只应该有1个,但是为了方便这里就当作有n个):方程 x n = 1 x^n=1 xn=1 的n个复数根,分别称为 w n 0 , w n 1 , w n 2 , . . . , w n n − 1 w_n^0,w_n^1,w_n^2,...,w_n^{n-1} wn0,wn1,wn2,...,wnn1 ,其中 w n 0 = 1 w_n^0=1 wn0=1 。简记为 w n , 0 w_{n,0} wn,0 。不引起歧义的情况下,我会写成 w 0 w_0 w0 。同时,在这里,n应该是2的幂次(原因后文会讲)。

主要用到的n次单位根的性质: w n , 1 k = w n , k , w n , k = w 2 ∗ n , 2 ∗ k , w n , k + n 2 = − w n , k w_{n,1}^k=w_{n,k},w_{n,k}=w_{2*n,2*k},w_{n,k+\frac{n}{2}}=-w_{n,k} wn,1k=wn,k,wn,k=w2n,2k,wn,k+2n=wn,k

DFT,就是把一个长度为 n n n 次的向量(多项式系数表示)代入 n n n 次单位根,从而转换为另一个向量(多项式点值表示)。而IDFT,是把以上点值表示转换为系数表示。它们的复杂度都是 O ( n log ⁡ n ) O(n\log n) O(nlogn)

做法

数学

好,假设我们现在需要把一个长为 2 l e n = n 2^{len}=n 2len=n 的多项式 A = ∑ i = 0 n − 1 a i ∗ x i A=\sum_{i=0}^{n-1}a_i*x^i A=i=0n1aixi 转换为点值表示(系数表示和系数表示向量被认为是相同的)。

我们先把它分成两部分:

A = ∑ i = 0 n 2 − 1 a 2 ∗ i ∗ x 2 ∗ i + ∑ i = 0 n 2 − 1 a 2 ∗ i + 1 ∗ x 2 ∗ x + 1 。 A=\sum_{i=0}^{\frac{n}{2}-1}a_{2*i}*x^{2*i}+\sum_{i=0}^{\frac{n}{2}-1}a_{2*i+1}*x^{2*x+1}。 A=i=02n1a2ix2i+i=02n1a2i+1x2x+1

构造两个新的多项式,其系数表示的向量分别为:

A 1 = { a 0 , a 2 , . . . , a n − 2 } A 2 = { a 1 , a 3 , . . . , a n − 1 } A_1=\{a_0,a_2,...,a_{n-2}\}\\ A_2=\{a_1,a_3,...,a_{n-1}\} A1={a0,a2,...,an2}A2={a1,a3,...,an1}

把这两个多项式分别变成点值表示(相当于递归向下做):

A 1 = { ( w n 2 , 0 , a 0 ) , ( w n 2 , 1 , a 2 ) , . . . , ( w n 2 , n 2 − 1 , a n − 2 ) } A 2 = { ( w n 2 , 0 , a 1 ) , ( w n 2 , 1 , a 2 ) , . . . , ( w n 2 , n 2 − 1 , a n − 2 ) } A_1=\{(w_{\frac{n}{2},0},a_0),(w_{\frac{n}{2},1},a_2),...,(w_{\frac{n}{2},\frac{n}{2}-1},a_{n-2})\}\\ A_2=\{(w_{\frac{n}{2},0},a_1),(w_{\frac{n}{2},1},a_2),...,(w_{\frac{n}{2},\frac{n}{2}-1},a_{n-2})\} A1={(w2n,0,a0),(w2n,1,a2),...,(w2n,2n1,an2)}A2={(w2n,0,a1),(w2n,1,a2),...,(w2n,2n1,an2)}

然后考虑我们需要的是A的点值表示。枚举每一个 n n n 阶单位根,我们需要知道A代入 w n i w_n^i wni 的值。然后再看一下第一个公式

A = ∑ i = 0 n 2 − 1 a 2 ∗ i ∗ x 2 ∗ i + ∑ i = 0 n 2 − 1 a 2 ∗ i + 1 ∗ x 2 ∗ x + 1 。 A=\sum_{i=0}^{\frac{n}{2}-1}a_{2*i}*x^{2*i}+\sum_{i=0}^{\frac{n}{2}-1}a_{2*i+1}*x^{2*x+1}。 A=i=02n1a2ix2i+i=02n1a2i+1x2x+1

又因为有 ( w n 1 ) 2 = w n 2 = w n 2 1 (w_n^1)^2=w_n^2=w_{\frac{n}{2}}^1 (wn1)2=wn2=w2n1 ,所以代入 w n i w_n^i wni 后,点值就是A1在 w n 2 i w_{\frac{n}{2}}^i w2ni 处的点值加上 w n i ∗ A 2 [ w n 2 i ] w_n^i*A_2[w_{\frac{n}{2}}^i] wniA2[w2ni]

然后是IDFT。有一个结论,是说只要把以上的 n n n 次单位根全都变成相反数就行。证明自行百度。

代码

inline void FFT(Complex *A,int len,int fl);

DFT中,A开始存系数,后来存 A [ w i ] A[w_i] A[wi]

基本代码:

inline void FFT(Complex *A,int len,int fl){
    if (len == 1) return;
    Complex *A1, *A2;
    A1 = new Complex[len / 2], A2 = new Complex[len / 2];
    rep(i, 0, len / 2){
        A1[i] = A[i * 2];
        A2[i] = A[i * 2 + 1];
    }
    FFT(A1, len / 2, fl);
    FFT(A2, len / 2, fl);
    Complex w = init(len, fl), cur = (Complex)1;
    rep(i, 0, len){
        A[i] = A1[i % (len / 2)] + cur * A2[i % (len / 2)];
        cur = cur * w;
    }
}

如果把最终的数字转换的结果放出来,就是这样的:

int tmp;
inline void FFT(Complex *A,int len,int fl){
    // reverse
    int j = 0;
    rep(i, 0, len){
        if (j > i) Swap(A[i], A[j]);
        // reverse add j
    }
    // reverse
    Complex w, step, tmp;
    for (i = 1; i < len; i <<= 1){
        w = ...;
        for (int j = 0; j < len; j += i * 2){
            step = (Complex)1;
            for (k = j; k < j + i; ++k){
                tmp = step * A[j + i + k];
                A[j + i + k] = A[j + k] - tmp;
                A[j + k] += tmp;
                step *= w;
            }
        }
    }
}

(上面的加减可能需要理解)

NTT

如上,但是把所有的 n n n 次单位复根换成 ± 3 998244352 / n m o d &ThinSpace;&ThinSpace; 998244353 \pm 3^{998244352/n}\mod{998244353} ±3998244352/nmod998244353

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值