任意模数 NTT 和 DFT 的优化
可能就存个板子,而且先咕了
来更了
参考 毛啸《再探快速傅里叶变换》
三模数 NTT
不说了
拆系数 FFT
描述
令 \(M=2^{15}=32768\),对于需要卷积的数列 \(A_i\) 和 \(B_i\) 中的每个数 \(x\),拆成 \(x=k\times M+b\) 的形式
当长度是 \(10^5\) ,模数在 \(10^9\) 左右时,最大的数字大约在 \(10^{14}\),单位根处理得好一点就不会爆
double
的精度
把 \(A_i\) 拆出的两个数列和 \(B_i\) 拆出的两个数列 DFT 后各选一个相乘,最终的系数分别是 \(1,2^{15},2^{15},2^{30}\),相同的两项可以一起 IDFT
总共需要 7 次 DFT
已经挺快的了
代码
就是下面的模板题
1 |
|
DFT 的优化
描述
假设 \(n\) 是 \(2\) 的整数次幂,现在需要对长度为 \(n\) 的多项式 \(A(x)\) 和 \(B(x)\) 进行 DFT,我们可以合并只做一次
做法
令
\[ \begin{align} P(x)=A(x)+iB(x) \\ Q(x)=A(x)-iB(x) \end{align} \]
设 \(P'[k]\) 和 \(Q'[k]\) 分别是 \(P(x)\) 和 \(Q(x)\) 进行 DFT 后的序列
有 \(P'[k]=P(\omega_n^k),Q'[j]=Q(\omega_n^k)\),即代入 \(n\) 次单位根的幂后的点值
推导直接拉了
令 \(\text{conj}(x)\) 表示 \(x\) 的共轭复数,\(A_i\) 即 \(A(x)\) 的 \(i\) 次项系数
\[ \begin{align} P'[k] &= A(\omega_{n}^{k}) + i B(\omega_{n}^{k}) \\ & = \sum_{j=0}^{n-1} A_{j} \omega_{n}^{jk} + i B_{j} \omega_{n}^{jk} \\ & = \sum_{j=0}^{n-1} (A_{j} + i B_{j}) \left(\cos \left(\frac{2 \pi jk}{n}\right) + i \sin \left(\frac{2 \pi jk}{n}\right)\right) \\ \\ Q'[k] &= A(\omega_{n}^{k}) - i B(\omega_{n}^{k}) \\ & = \sum_{j=0}^{n-1} A_{j} \omega_{n}^{jk} - i B_{j} \omega_{n}^{jk} \\ & = \sum_{j=0}^{n-1} (A_{j} - i B_{j}) \left(\cos \left(\frac{2 \pi jk}{n}\right) + i \sin \left(\frac{2 \pi jk}{n}\right)\right) \\ & = \sum_{j=0}^{n-1} \left(A_{j} \cos \left(\frac{2 \pi jk}{n}\right) + B_{j} \sin \left(\frac{2 \pi jk}{n}\right)\right) + i \left(A_{j} \sin \left(\frac{2 \pi jk}{n}\right) - B_{j} \cos \left(\frac{2 \pi jk}{n}\right)\right) \\ & = \text{conj} \left( \sum_{j=0}^{n-1} \left(A_{j} \cos \left(\frac{2 \pi jk}{n}\right) + B_{j} \sin \left(\frac{2 \pi jk}{n}\right)\right) - i \left(A_{j} \sin \left(\frac{2 \pi jk}{n}\right) - B_{j} \cos \left(\frac{2 \pi jk}{n}\right)\right) \right) \\ & = \text{conj} \left( \sum_{j=0}^{n-1} \left(A_{j} \cos \left(\frac{-2 \pi jk}{n}\right) - B_{j} \sin \left(\frac{-2 \pi jk}{n}\right)\right) + i \left(A_{j} \sin \left(\frac{-2 \pi jk}{n}\right) + B_{j} \cos \left(\frac{-2 \pi jk}{n}\right)\right) \right) \\ & = \text{conj} \left( \sum_{j=0}^{n-1} (A_{j} + i B_{j}) \left(\cos \left(\frac{-2 \pi jk}{n}\right) + i \sin \left(\frac{-2 \pi jk}{n}\right)\right)\right) \\ & = \text{conj} \left( \sum_{j=0}^{n-1} (A_{j} + i B_{j}) \omega_{n}^{-jk} \right) \\ & = \text{conj} \left( \sum_{j=0}^{n-1} (A_{j} + i B_{j}) \omega_{n}^{(n-k)j} \right) \\ & = \text{conj} (P'[n-k]) \end{align} \]
于是我们可以通过 \(P(x)\) 得到 \(Q(x)\)
注意最后得到的 \(n-k\) 是模 \(n\) 意义下的,当 \(k=0\) 时需要特殊处理
设 \(A'[k]\) 和 \(B'[k]\) 分别是 \(A(x)\) 和 \(B(x)\) 进行 DFT 后的序列
有
\[ \begin{align} A'[k]=\frac{P'[k]+Q'[k]}{2} \\ B'[k]=\frac{P'[k]-Q'[k]}{2i} \end{align} \]
通过这种方法可以用一次长度不变的 DFT 同时计算两个序列 DFT 后的结果
假设对 \(P'[k]\) 进行了 IDFT,实部和虚部分别就是 \(A(x)\) 和 \(B(x)\) 了
单个 DFT 的优化
可以分奇次项和偶次项拆成两个序列,可以用一次原先一半长度的 DFT
来得到 DFT 的结果,我就不学了
update: 来学了
半次 DFT
DFT
考虑把多项式 \(f(x)\) 的偶次系数和奇次系数分开得到两个一半长度的序列,当做多项式分别是 \(f_0(x)\) 和 \(f_1(x)\)
于是
\[ \begin{align} f(x) &= f_0(x^2)+x \times f_1(x^2) \\ \\ f(\omega_n^k) &= f_0(\omega_n^{2k})+\omega_n^k \times f_1(\omega_n^{2k}) \\ &= f_0(\omega_{\frac{n}{2}}^k)+\omega_n^k \times f_1(\omega_{\frac{n}{2}}^k) \end{align} \]
我们把两个一半长度的多项式合并做 DFT,通过上式可以算出原多项式 DFT 的结果
IDFT
几乎是反的,用 \(f(\omega_n^k)\) 和 \(f(\omega_n^{k+\frac{n}{2}})\) 可以解出 \(f_0(\omega_{\frac{n}{2}}^k)\) 和 \(f_1(\omega_{\frac{n}{2}}^k)\)
然后那样塞回一半长度的序列,做 IDFT 后实部和虚部分别就是偶次和奇次的系数了
实现可以参考 \(3.5\) 次的模板
应用
在上面的拆系数 FFT 中可以优化到 \(4\) 次或者所谓的 \(3.5\) 次的 DFT
模板
代码
\(4\) 次 DFT
1 |
|
\(3.5\) 次 DFT
1 |
|
例题
贴代码,方便以后拉
多项式求逆
代码
1 |
|
传球
代码
1 |
|
Flair
LOJ #6132. 「2017 山东三轮集训 Day1」Flair
性质
令最大的两个数的乘积为 \(a\),所有数的 \(\gcd\) 为 \(b\),选择了 \(i\) 个的浪费是 \(f_i\)
对于 \(i\ge a\) 有 \(f_i=f_{i+b}\)
那么我们做长度为 \(b\) 的循环卷积快速幂,对于 \(a\) 以内的特判就好了
考试的时候没带脑子
有些细节不管复杂度就是 \(\mathcal O(b\log b\log n + m\times a)\)
代码
1 |
|
任意模数 NTT 和 DFT 的优化