概念
FFT全称Fast Fourier Transformation, 即快速傅里叶变换,可在 \(O(n \log n)\) 的复杂度计算多项式乘法
一般的多项式乘法是这样的:
\[\begin{aligned} & \ (x^2 + 2x + 1)(3x ^ 2 - x + 7) \\ =& \ x ^ 2(3x ^ 2 - x + 7) + 2x(3x ^ 2 - x + 7) + (3x ^ 2 - x + 7) \\ =& \ 3x ^ 4 - x ^ 3 + 7x ^ 2 + 6x ^ 3 - 2 x ^ 2 + 14x + 3x ^ 2 - x + 7 \\ =& \ 3x ^ 4 + 5x ^ 3 + 8x ^ 2 + 13x + 7 \end{aligned} \]注意到这样是 \(O(n ^ 2)\) 的,慢的一批
FFT 基本思想
- 多项式的表达方法
- 系数表示法:\(F(x)\) 表示一个形如 \(a_{n} x^{n} + a_{n - 1} x^{n - 1} + \cdots + a_{1} x + a_{0}\) 的多项式,其中 \(a\) 是系数,且通常把 \(a_{i}\) 写作 \(F_i\) 。上式也可以写作 \(\sum_{i = 0}^{n} F_i x ^i\)
- 点值表示法:注意到平面上的 \(n + 1\) 个点可以唯一确定一个 \(n\) 次多项式,所以我们可以用这 \(n + 1\) 个点表示一个多项式
- 卷积
- 注意到令 \(C = A \times B\) (其中 \(A, B, C\) 均为多项式),则 \(C_k = \sum_{i = 0}^{k} A_{i} \times B_{k - i} = \sum\limits_{i + j = k} A_{i} \times B_{j}\)
- 定义形如 \(C_k = \sum\limits_{i \oplus j = k} A_{i} \times B_{j}\) 的式子称为卷积(其中 \(\oplus\) 为运算符号)
- 显然,多项式乘法就是加法卷积
- 点值表示法的乘法运算
- 点值表示法下,多项式的乘法即为这两个多项式对应点的 \(y\) 坐标的乘积
- 例如我们可以用 \((1, 4), (2, 9), (3, 16)\) 来表示 \(x ^ 2 + 2x + 1\) ,用 \((1, 9), (2,17), (3, 31)\) 表示 \(3x ^ 2 - x + 7\) ,那么这两个多项式的结果 \(3x ^ 4 + 5x ^ 3 + 8x ^ 2 + 13x + 7\) 均过点 \((1, 36), (2, 153), (3, 496)\) 。但是这 \(3\) 个点无法表示结果,我们再加入一些点就行了。
- 注意到点值表示法下,多项式乘法的时间复杂度是线性的
- 那么我们可以利用这个性质,将原式用点值表示法表示后相乘,在转化为系数表达法,这就是 FFT 的算法流程。其中系数表示法转点值表示法称为 DFT ,点值表示法转系数表示法称为 IDFT(DFT的逆运算)
单位根及其性质
前置芝士:复数(普通高中教科书·数学(A版)必修 第二册 第七章)
附上复数结构体的代码
struct Complex {
double x, y;
inline Complex(const double xx = 0, const double yy = 0) {
x = xx, y = yy;
}
inline Complex operator + (const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
inline Complex operator - (const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
inline Complex operator * (const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
inline Complex operator / (const Complex &b) const {
double t = b.x * b.x + b.y * b.y;
return Complex((x * b.x + y * b.y) / t, (y * b.x - x * b.y) / t);
}
};
定义 \(n\) 次单位根 ( \(n \in Z\) )是方程 \(x^n = 1\) 的复数解
亦可将 \(x\) 表示为 \(r^n(\cos{n \theta} + i\sin{n \theta})\)
不难发现方程的每个根都是单位圆上的 \(n\) 等分点
我们从 \(1\) 开始,依次将这些点表示为 \(\omega_n^0, \omega_n^1, \cdots, \omega_n^{n-1}\)
一些性质:
- 对于任意的 \(n\) ,都有 \(\omega_n^0 = 1\)
- \(\omega_n^k = (\omega_n^1)^k\)
- \(\omega_n^j \times \omega_n^k = \omega_n^{j + k}\)
- \(\omega_{pn}^{pk} = \omega_n^k\)
- \(\forall \ 2 | n \ ,\omega_n^{k + \frac{n}{2}} = -\omega_n^k\)
- \(\omega_n^k = \omega_n^{k \bmod n}\)
DFT的基本思想
下面保证 \(n = 2^k (k \in N^{\ast})\)
若不满足,补上剩下的项即可(补的项系数均为 \(0\) )
我们先将 \(n - 1\) 次多项式的系数按照奇偶拆分为这样
\[\begin{aligned} F(x) &= F_0 + F_1 x +\cdots + F_n x^n \\ &= (F_0 + F_2 x ^ 2 + \cdots + F_{n - 2}) + (F_1 x + F_3 x ^ 3 + \cdots + F_{n - 1} x^{n - 1}) \end{aligned} \]令
\[A = F_0 + F_2 x + \cdots + F_{n - 2} x ^{\frac{x}{2} - 1} \\ B = F_1 + F_3 x + \cdots + F_{n - 1} x ^{\frac{x}{2} - 1} \]则
\[F(x) = A(x ^ 2) + xB(x ^ 2) \]我们将 \(\omega_n^k , (k < \dfrac{n}{2})\) 带入,得
\[F(\omega_n^k) = A((\omega_n^k) ^ 2) + \omega_n^k B((\omega_n^k) ^ 2) \]由 \((\omega_n^k)^2 = \omega_n^{2k} = \omega_{\frac{n}{2}}^k\) ,得
\[F(\omega_n^k) = A(\omega_{\frac{n}{2}}^k) + \omega_n^k B(\omega_{\frac{n}{2}}^k) \]我们将 \(\omega_n^{k + \frac{n}{2}} , (k < \dfrac{n}{2})\) 带入 \(F(x) = A(x ^ 2) + xB(x ^ 2)\) ,得
\[F(\omega_n^{k + \frac{n}{2}}) = A((\omega_n^{k + \frac{n}{2}}) ^ 2) + \omega_n^{k + \frac{n}{2}} B((\omega_n^{k + \frac{n}{2}}) ^ 2) \]由 \((\omega_n^j)^k = \omega_n^{jk}\), 得
\[F(\omega_n^{k + \frac{n}{2}}) = A(\omega_n^{2k + n}) + \omega_n^{k + \frac{n}{2}} B(\omega_n^{2k + n}) \]由 \(\omega_n^k = \omega_n^{k \bmod n}\) ,得
\[F(\omega_n^{k + \frac{n}{2}}) = A(\omega_n^{2k}) + \omega_n^{k + \frac{n}{2}} B(\omega_n^{2k}) \]由 \(\omega_n^{2k} = \omega_{\frac{n}{2}}^k\) ,得
\[F(\omega_n^{k + \frac{n}{2}}) = A(\omega_{\frac{n}{2}}^k) + \omega_n^{k + \frac{n}{2}} B(\omega_{\frac{n}{2}}^k) \]由 \(\omega_n^{k + \frac{n}{2}} = -\omega_n^k\) ,得
\[F(\omega_n^{k + \frac{n}{2}}) = A(\omega_{\frac{n}{2}}^k) - \omega_n^kB(\omega_{\frac{n}{2}}^k) \]对比上一个式子 \(F(\omega_n^k) = A(\omega_{\frac{n}{2}}^k) + \omega_n^k B(\omega_{\frac{n}{2}}^k)\) ,发现他们的区别仅仅只有 \(B\) 的正负
不难发现,若我们已知 \(A, B\) 在 \(\omega_{\frac{n}{2}}^0, \omega_{\frac{n}{2}}^1, \cdots,\omega_{\frac{n}{2}}^{\frac{n}{2} - 1}\) 的 点值表示,就可以在线性时间复杂度得出 \(\omega_{n}^0, \omega_{n}^1, \cdots,\omega_{n}^{n - 1}\) ,即 \(F(x)\) 的点值表示
那么我们就可以不断分治求解DFT
代码:
void DFT(Complex *f, int len) {
if (len == 1)
return ;
Complex *A = f, *B = f + (len >> 1);
for (int i = 0; i < len; ++i)
tmp[i] = f[i];
for (int i = 0; i < (len >> 1); ++i) {
A[i] = tmp[i << 1];
B[i] = tmp[i << 1 | 1];
}
DFT(A, len >> 1), DFT(B, len >> 1);
Complex tG(cos(2 * Pi / len), sin(2 * Pi / len)), buf(1, 0);
for (int i = 0; i < (len >> 1); ++i) {
Complex tt = buf * B[i];
tmp[i] = A[i] + tt;
tmp[i + (len >> 1)] = A[i] - tt;
buf = buf * tG;
}
for (int i = 0; i < len; ++i)
f[i] = tmp[i];
}
IDFT的基本思想
我们令用单位根点值表达的多项式 \(F(x)\) 的系数表达式为 \(G(x)\) ,则有 \(G_k = \sum_{i = 0}^{n - 1} (\omega_n^k)^i F_i\)
结论: \(n F_k = \sum_{i = 0}^{n - 1} (\omega_n^{-k})^iG_i\)
证明:
\[\begin{aligned} 右边 &= \sum_{i = 0}^{n - 1} (\omega_n^{-k})^iG_i \\ &= \sum_{i = 0}^{n - 1} \omega_n^{-ki}(\sum_{j = 0}^{n - 1} \omega_n^{ij} F_j) \\ &= \sum_{i = 0}^{n - 1} \sum_{j = 0}^{n - 1} \omega_n^{-ki} \omega_n^{ij} F_j \\ &= \sum_{i = 0}^{n - 1} \sum_{j = 0}^{n - 1} \omega_n^{i(j - k)} F_j \end{aligned} \]当 \(j = k\) 时,原式 \(=\sum_{i = 0}^{n - 1}\omega_n^0F_k = nF_k\)
当 \(j \not = k\) 时,设 \(t = j - k\) ,则原式 \(= \sum_{i = 0}^{n - 1}\omega_n^{ip}F_{k + p} = \omega_n^p(\sum_{i = 0}^{n - 1}\omega_n^i)F_{k + p}\)
注意到 \(\sum_{i = 0}^{n - 1}\omega_n^i\) 实际就是等比数列求和,利用等比数列求和公式得到
\[\sum_{i = 0}^{n - 1}\omega_n^i = \dfrac{\omega_n^0 - \omega_n^n}{1 - \omega_n^1} = \dfrac{\omega_n^0 - \omega_n^0}{1 - \omega_n^1} = 0 \]所以此时原式的值为 \(0\)
综上所述,\(n F_k = \sum_{i = 0}^{n - 1} (\omega_n^{-k})^iG_i\)
再看这个结论,实际就是把 \(G\) 当作系数,带入 \(\{\omega_n^0, \omega_n^{-1}, \cdots, \omega_n^{-n + 1}\}\) 求值
因为 \(\omega_n^{-n + 1} = \cos{\dfrac{2 \pi}{n}} + i \sin{\dfrac{2 \pi}{n}}\) ,我们直接利用 \((\omega_n^{-1})^k = \omega_n^{-k}\) 求值即可
代码和 DFT区别不大,只要把 tG
的 sin
前面加上负号即可
代码:
#include <bits/stdc++.h>
using namespace std;
const double Pi = acos(-1);
const int N = 2e6 + 7;
struct Complex {
double x, y;
inline Complex(const double xx = 0, const double yy = 0) {
x = xx, y = yy;
}
inline Complex operator + (const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
inline Complex operator - (const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
inline Complex operator * (const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
inline Complex operator / (const Complex &b) const {
double t = b.x * b.x + b.y * b.y;
return Complex((x * b.x + y * b.y) / t, (y * b.x - x * b.y) / t);
}
} f[N << 1], p[N << 1], tmp[N << 1];
int n, m;
void FFT(Complex *f, int len, int sign) {
if (len == 1)
return ;
Complex *A = f, *B = f + (len >> 1);
for (int i = 0; i < len; ++i)
tmp[i] = f[i];
for (int i = 0; i < (len >> 1); ++i) {
A[i] = tmp[i << 1];
B[i] = tmp[i << 1 | 1];
}
FFT(A, len >> 1, sign), FFT(B, len >> 1, sign);
Complex tG(cos(2 * Pi / len), sin(2 * Pi / len) * sign), buf(1, 0);
for (int i = 0; i < (len >> 1); ++i) {
Complex tt = buf * B[i];
tmp[i] = A[i] + tt;
tmp[i + (len >> 1)] = A[i] - tt;
buf = buf * tG;
}
for (int i = 0; i < len; ++i)
f[i] = tmp[i];
}
signed 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", &p[i].x);
for (m += n, n = 1; n <= m; n <<= 1);
FFT(f, n, 1), FFT(p, n, 1);
for (int i = 0; i < n; ++i)
f[i] = f[i] * p[i];
FFT(f, n, -1);
for (int i = 0; i <= m; ++i)
printf("%d ", (int) (f[i].x / n + 0.5));
return 0;
}
优化
可以发现这种写法常数非常大
我们尝试观察一下这个递归分组的过程
0 1 2 3 4 5 6 7
0 2 4 6|1 3 5 7
0 4|2 6|1 5|3 7
0|4|2|6|1|5|3|7
注意到递归后 \(6\) 排在了 \(3\) 的位置,且 \(6 = (110)_2, 3 = (011)_2\)
观察其他的数字后,我们发现递归的本质就是二进制翻转
怎么求呢?我们递推实现
我们设 \(i\) 二进制翻转后的数为 \(tr_i\)
假设我们现在要求 \(i\) 二进制翻转后的数,注意到 \(i = (\cdots x)_2 (x = i \bmod 2)\)
那么有 \(tr_i = (x\cdots)_2\) ,其中 \(\cdots\) 的部分我们直接用之前求出的 \(tr\) 值求解即可
代码:
for (int i = 0; i < n; ++i)
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? (n >> 1) : 0);
为了减小常数,我们甚至可以化递归为迭代,也就是这样
#include <bits/stdc++.h>
using namespace std;
const double Pi = acos(-1);
const int N = 5e6 + 7;
struct Complex {
double x, y;
inline Complex(const double xx = 0, const double yy = 0) {
x = xx, y = yy;
}
inline Complex operator + (const Complex &b) const {
return Complex(x + b.x, y + b.y);
}
inline Complex operator - (const Complex &b) const {
return Complex(x - b.x, y - b.y);
}
inline Complex operator * (const Complex &b) const {
return Complex(x * b.x - y * b.y, x * b.y + y * b.x);
}
inline Complex operator / (const Complex &b) const {
double t = b.x * b.x + b.y * b.y;
return Complex((x * b.x + y * b.y) / t, (y * b.x - x * b.y) / t);
}
} f[N], p[N];
int tr[N];
int n, m, limit, highest;
inline void FFT(Complex *f, int sign) {
for (int i = 0; i < limit; ++i)
if (i < tr[i])
swap(f[i], f[tr[i]]);
for (int p = 2; p <= limit; p <<= 1) {
int len = p >> 1;
Complex tG(cos(2 * Pi / p), sin(2 * Pi / p) * sign);
for (int k = 0; k < limit; k += p) {
Complex buf(1, 0);
for (int l = k; l < k + len; ++l) {
Complex tt = buf * f[len + l];
f[len + l] = f[l] - tt;
f[l] = f[l] + tt;
buf = buf * tG;
}
}
}
}
signed 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", &p[i].x);
limit = 1, highest = n + m;
while (limit <= highest)
limit <<= 1;
for (int i = 0; i < limit; ++i)
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? (limit >> 1) : 0);
FFT(f, 1), FFT(p, 1);
for (int i = 0; i < limit; ++i)
f[i] = f[i] * p[i];
FFT(f, -1);
for (int i = 0; i <= highest; ++i)
printf("%d ", (int) (f[i].x / limit + 0.5));
return 0;
}
标签:frac,int,FFT,len,Complex,const,omega
From: https://www.cnblogs.com/wshcl/p/17134184.html