引入
傅里叶变换是把函数从时间域转化到频域的数学变换,FFT是其快速实现方式。本文主要讨论的是FFT在ACM竞赛中的应用。
FFT用来解决多项式相乘问题,可以把 $O(n^2)$ 的时间复杂度优化到 $O(nlg(n))$ 。
一个 $n$ 次多项式可以表示为 $F(x) = \sum_{i=0}^{n}a_{i}x^i$ ,现在再有一个多项式 $G(x)$ ,让求两个多项式相乘 $C(x)$ 的系数,有 \(c_{k} = \sum_{i+j=k}a_ib_j\) 其本质是加法卷积。两个多项式的卷积写作 \(c_k = \sum_{i \bigoplus j=k}a_ib_j\) 其中 $\bigoplus$ 代表某种运算,在多项式乘法中就代表了加法。还有位运算等形式,会在之后介绍。
根据上述理论,有多项式乘法的暴力实现代码, $O(n^2)$ :
1 |
|
点值表示法
先把多项式化为函数 \(F(x) = \sum_{i=0}^{n}a_ix^i,G(x) = \sum_{i=0}^{n}b_ix^i\) 现在要求 $C(x) = \sum_{i=0}^{2n}c_ix^i$ 的系数 $c_i$ 。
我们知道用 $n+1$ 个点可以确定一个 $n$ 次函数,取 $n+1$ 点 $x_i$ 带入 $F(x)$ 和 $G(x)$ 可以得到对应的 $y$ 值,用这 $n+1$ 个点也可以唯一的表示 $F(x)$ 和 $G(x)$ 。因为 $C(x_i) = F(x_i)G(x_i)$ ,所以可以$O(n)$ 的得到 $C(x)$ 的 $n+1$ 个点,但是因为 $C(x)$ 的最高幂为 $n+m$,若想唯一的表示 $C(x)$ 需要 $2n+1$ 个点,所以可以对 $F(x)$ 和 $G(x)$ 取 $2n+1$ 个点,时间复杂度依然不变,所以接下来就要考虑如何把系数表示法转化为点值表示法。
其中把系数表示法转化为点值表示法称作 $DFT$ ,点值表示转化成系数表示称作 $IDFT$ 。而 $FFT$ 就是 $DFT$ 的加速版本。
单位根的性质
以下性质在推到中会用到,在这不做具体证明。
-
$\omega_{n}^{1}$ 的在复数系的坐标为 $(cos\dfrac{2π}{n},sin\dfrac{2π}{n})$ 即 $\omega_{n}^{1} = cos\frac{2\pi}{n} + isin\frac{\pi}{n}$。
- $\omega_{n}^{k} = \omega_{n}^{k\%n}$
- $\omega_{n}^{k1}\omega_{n}^{k2} = \omega_{n}^{k1+k2}$
- $\omega_{2n}^{2k} = \omega_{n}^{k}$
- $\omega_{n}^{k+n/2} = -\omega_{n}^{k}$ $(n是偶数)$
DFT
把 $n-1$ 次(也叫 $n$ 项)多项式拆分成奇偶两部分 \(F(x) = (a_0+a_2x^2+...+a_{n-2}x^{n-2})+(a_1x+a_3x^3+...+a_{n-1}x^{n-1})\) 这里为了保证可以让两者平均分,必须保证 $n$ 是 $2$ 的倍数。 不足的高次幂可以补 $0$ 。
设下面两个 $\frac{n}{2}$ 项函数 \(L(x) = a_0 + a_2x + ... + a_{n-2}x^{\frac{n}{2}-1} \\ R(x) = a_1 + a_3x + ... + a_{n-1}x^{\frac{n}{2}-1}\) 则 \(F(x) = L(x^2) + xR(x^2)\) 现在要代入 $n$ 次单位根 $\omega_{n}^{k}$ $(k<\frac{n}{2})$ ,即考虑 $(\omega_{n}^{k},y_k)$ $( k<\frac{n}{2} )$ 这些点: \(F(\omega_{n}^{k}) = L((\omega_{n}^{k})^2) + \omega_{n}^{k}R((\omega_{n}^{k})^2)\) 因为 $(\omega_{n}^{k})^2 = \omega_{\frac{n}{2}}^{k}$ ,所以 \(F(\omega_{n}^{k}) = L(\omega_{n/2}^{k}) + \omega_{n}^{k}R(\omega_{n/2}^{k})\) 目前只考虑了$(\omega_{n}^{k},y_k)$ $( k<\frac{n}{2} )$ 这些点,下面考虑 $(\omega_{n}^{k},y_k)$ $( k\geq \frac{n}{2} )$ 的点
把 $\omega_{n}^{k+n/2}$ $(k<\frac{n}{2})$代入: \(F(\omega_{n}^{k+n/2}) = L((\omega_{n}^{k+n/2})^2) + \omega_{n}^{k+n/2}R((\omega_{n}^{k+n/2})^2) \\ = L(\omega_{n}^{2k+n}) + \omega_{n}^{k+n/2}R(\omega_{n}^{2k+n})\\ = L(\omega_{n}^{2k}) + \omega_{n}^{k+n/2}R(\omega_{n}^{2k}) \\ = L(\omega_{n/2}^{k}) + \omega_{n}^{k+n/2}R(\omega_{n/2}^{k}) \\ = L(\omega_{n/2}^{k}) - \omega_{n}^{k}R(\omega_{n/2}^{k}) \\\) 可以观察到两次代入得出来的结果只差了一个正负号,也就是可用相似的代码解决.
此时若想求出 $(\omega_{n}^{k},y_k)$ $(0\leq k \leq n-1)$ ,可以减少一半,算出 \((\omega_{n/2}^{k},y_k)\) 即可通过上式得出 \((\omega_{n}^{k},y_k)\) 的值,考虑递归求解,这时就要保证每次递归的时候都能把新的 $ n$ 平均分成两半,即原来的 $n$ 必须是 $2$ 的正整数次幂 ,不足的用 $0$ 补全高次。
根据上述推导可以写出 $FFT$ 的代码:
1 |
|
IDFT
上述操作是把多项式化为了点值表示法,若想知道多项式的各系数还要把点值表示法转化为系数表示法。
系数表示法到点值表示法(DFT)有 \(Y(k) = \sum_{i=0}^{n-1}(\omega_n^k)^iF(i)\) IDFT 有 \(nF(k) = \sum_{i=0}^{n-1}(\omega_n^{-k})^iF(i)\) 仅有带入的单位根不同并多了一个 $n$ ,在此不做证明了(懒得打了),结合上一份代码有:
1 |
|
$op$ 取 $ 1$是 $DFT$,取0是 $IDFT$,要记得 $IDFT$ 后要除以 $n$ 。
迭代优化写法
由于递归的常数太大,又有人发明了递归的写法,具体证明不做介绍,直接贴代码
1 |
|
假设我们需要求 F(x)G(x)F(x)∗G(x*)
设复多项式 $P(x)=F(x)+iG(x)$ 也就是实部为 $F(x)$ , 虚部为 $G(x) $ .
则 $P(x)^2=(F(x)+iG(x))^2=F(x)^2-G(x)^2+2iF(x)G(x)$
发现 $P(x)^2$ 的虚部为 $2F(x)G(x)$
也就是说求出 $P(x)^2 $ 之后, 把它的虚部除以 $2n$ 即可.
减少依次调用写法
1 |
|
NTT
由于计算过程中会用到三角函数库,而且还要使用复数,计算比较慢,而且浮点数会丢失精度。若遇到一些让输出答案 $\mod p$ 的题目可以使用 $NTT$ 。
1 |
|
多项式的各种算法
多项式乘法
上方的NTT即可完成,封装代码:
1 |
|
多项式的逆(除法) (模数为NTT数)
公式 $R(X)=2R^(X)-R^(X)^2F(X)(mod\ x^n)$
1 |
|
任意模数的多项式求逆
1 |
|
多项式的牛顿迭代和微积分
…
多项式开方
公式: $B(x)=\frac{A(x)+B^∗(x)^2}{2B^∗(x)}$
1 |
|
多项式带余除法
参考博客
[洛谷日报 | 傅里叶变换(FFT)学习笔记](https://www.luogu.org/blog/command-block/fft-xue-xi-bi-ji) |
[洛谷日报 | NTT与多项式全家桶](https://www.luogu.org/blog/command-block/ntt-yu-duo-xiang-shi-quan-jia-tong) |
-
原根表:http://blog.miskcoo.com/2014/07/fft-prime-table ↩