FFT
FFT 流程
-
一个数字可以看成 \(a_i\times 10^i\) ,那么把两个数字转成多项式的形式,求最后每一项的系数
-
我们知道一个多项式可以通过 \(n\) 个系数,或者 \(n+1\) 个点值来确定,那么我们考虑快速的让两个多项式转换成点值表示,然后点值的值是可以直接乘起来的,然后再通过得到的 \((f*g)\) 的 \(n+1\) 个点值转回系数
-
FFT 要求长度为 \(2^n\) ,所以不够的补 0
系数转点值
-
对于函数 \(f(x)=a_0+a_1x+a_2x^2+...\)
-
\(f(x)=(a_0+a_2x^2+a_4x^4+...)+x(a_1+a_3x^2+a_5x^4+...)\)
-
设 \(G(x)=a_0+a_2x+a_4x^2...,H(x)=a_1+a_3x+a_5x^2...\)
-
那么 \(f(x)=G(x^2 )+xH(x^2)\)
-
注意到这个 \(x\) 我们取单位根 \(w_n^k,0\leq k\leq n\) ,之所以这个取是因为单位根是 \(x^n=1\) 在复数域下的解,幂次最后得出是 1 会方便我们计算
-
单位根有一些性质:
- \(w_n^n=1\)
- \(w_{2n}^{2k}=w_n^k\)
- \(w_{2n}^{n+k}=-w_{2n}^k\)
-
那么最后就会得到
- \(0<x<\frac{n}{2},f(w_n^x)=G(w_{n/2}^k)+w_n^kH(w_{n/2}^k)\)
- \(\frac{n}{2}\leq x<n,f(w_n^x)=G(w_{n/2}^k)-w_n^kH(w_{n/2}^k)\)
-
可以发现中间的符号不同,其他都是一样的,那么我们只要递归求 \(G(x)\) 和 \(H(x)\) 就可以了
-
最后得到的函数的每一位是相应的点值
-
复杂度为 \(O(n\log n)\) ,递归写法
蝴蝶变换
-
观察发现编号为 \(x(2)\) 的下标,最后会变到 \(rev(x(2))\) 的下标
-
这个可以预处理出来,将每个数变换到对应的地方,那么就可以直接递推处理
-
具体的变换可以参照
rev[i]=(rev[i>>1]>>1)|((i&1)<<(L-1));
点值转系数
-
实际上这个时候一般的高斯消元可以列出一个矩阵,具体可见 OI-Wiki
-
我们惊喜的发现这个系数矩阵是可逆的,并且逆矩阵的每个位置就是原来的数倒一下再除以 n
-
这个除以 n 可以放在最后再一起除
-
然后此时已经求出的点值乘上逆矩阵就等于原系数
-
我们将点值看成系数,系数看成新的点值,那么就转换成了之前的 快速系数转点值
-
因为只是倒一下,所以符号上有一点不同,其他的都是一样的
8 次MTT
-
FFT 的精度损失是一方面,另一方面无法像 NTT 那样做大值域,因为没办法取模
-
于是就有了拆系数 FFT ,也叫 MTT
-
以 P4245 【模板】任意模数多项式乘法 为例
-
一种方法是直接将一个函数 \(f(x)\) 拆成 \(f(x)/M\) ,\(f(x)\%M\)
-
\(f(x)=M\times (f(x)/M)+(f(x)\%M)=Mf_1(x)+f_2(x)\)
-
那么 \(f(x)g(x)=M^2f_1g_1+f_2g_2+Mf_1g_2+Mf_2g_1\) ,那么就要做 8 次 FFT
-
\(M\) 取 \(2^{15}\) 最好,开
long double
信仰的跑,值域是解决了,但是常数巨大
4 次 MTT
-
首先还是像上面一样拆式子,那么现在有 \(A_0,A_1,B_0,B_1\) 四个系数式,我们希望通过 2 次 DFT 得到它们各自的点值
-
构造系数式 \(P=A_0+iB_0,Q=A_0-iB_0\)
-
注意到 \(P,Q\) 的每一项都共轭,那么它们的点值也共轭,那么我们求出 \(P\) 的点值,然后推出 \(Q\) 的点值,然后类似于解一元二次方程来解出 \(A_0,B_0\)
-
那么分成两组,一共就是 2 次 DFT
-
现在得到了 \(A_0,A_1,B_1,B_0\) 的点值
-
更抽象的,现在有 4 个点值式 \(A,B,C,D\) ,我们希望通过 2 次 IDFT 得到它们两两乘积的系数式
-
构造 \(P=A+iB,Q=C+iD,Q'=C-iD\)
-
那么 \(PQ=AC+iBC+iDA-BD,PQ'=AC+iBC-iDA+BD\)
-
通过 2 次 IDFT 可以得到上述式子
-
因为最终的答案不存在虚部,那么就可以通过虚部和实部分别解出 \(AC,BC,BD,DA\) ,正好符合我们的要求
-
那么总的就是要进行 4 次 FFT