FFT
前置知识:复数、原根、卷积。
对于
它们的乘法是
因此计算多项式的乘积需要
点值表示法
考虑选取
于是求多项式的乘法,可以先从系数表示法转换为点值表示法,做完乘法再变回去。
单位原根
称方程
即
恰全是单位元的
DFT
设多项式
为
DFT 存在逆变换(IDFT),即从点值重新变回系数,仍是从向量到向量的变换。
IDFT 存在性质
篇幅所限,不放证明。现在我们可以统一的处理 DFT 和 IDFT。
分治
利用单位元根的特殊性,我们可以分治计算 DFT。比如对于
奇偶分别建立函数
则原来的函数可以表示为
一般的,对于度小于
同理可得
因此我们需要把多项式的系数向上补到
在 DFT 中使用有
至此,我们可以写出递归版的 FFT。
void DFT(Comp *f, int n, int type) {
if (n == 1)
return;
for (int i = 0; i < n; i++)
tmp[i] = f[i];
for (int i = 0; i < n; i++) { // 偶数放左边,奇数放右边
if (i & 1)
f[n / 2 + i / 2] = tmp[i];
else
f[i / 2] = tmp[i];
}
Comp *g = f, *h = f + n / 2;
DFT(g, n / 2, type), DFT(h, n / 2, type);
Comp step(cos(2 * PI / n), sin(2 * PI * type / n));
Comp cur(1, 0);
for (int k = 0; k < n / 2; k++) {
tmp[k] = g[k] + cur * h[k];
tmp[k + n / 2] = g[k] - cur * h[k];
cur = cur * step;
}
for (int i = 0; i < n; i++)
f[i] = tmp[i];
}
void DFT(Comp *f, int n, int type) {
if (n == 1)
return;
for (int i = 0; i < n; i++)
tmp[i] = f[i];
for (int i = 0; i < n; i++) { // 偶数放左边,奇数放右边
if (i & 1)
f[n / 2 + i / 2] = tmp[i];
else
f[i / 2] = tmp[i];
}
Comp *g = f, *h = f + n / 2;
DFT(g, n / 2, type), DFT(h, n / 2, type);
Comp step(cos(2 * PI / n), sin(2 * PI * type / n));
Comp cur(1, 0);
for (int k = 0; k < n / 2; k++) {
tmp[k] = g[k] + cur * h[k];
tmp[k + n / 2] = g[k] - cur * h[k];
cur = cur * step;
}
for (int i = 0; i < n; i++)
f[i] = tmp[i];
}
蝴蝶变换
递归分治总是不尽人意的,如果能一次到位就更好了。还是以
- 初始
- 一次
- 两次
- 结束
写出二进制的形式,可以发现规律
结束和开始的二进制恰好是相反的。这个变换称为蝴蝶变换,也称位逆序置换(bit-reversal permutation)。
我们可以 R(x)
是 R(x>>1)
是已求的。
即是把 R(x>>1)
右移一位再补上最高位即可。代码如下
int lim = 1, lim_2;
while (lim <= n + m)
lim <<= 1;
lim_2 = lim >> 1;
for (int i = 0; i < lim; ++i) {
rev[i] = rev[i >> 1] >> 1;
if (i & 1)
rev[i] |= lim >> 1;
// 或者合并写为
// rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * lim_2);
}
int lim = 1, lim_2;
while (lim <= n + m)
lim <<= 1;
lim_2 = lim >> 1;
for (int i = 0; i < lim; ++i) {
rev[i] = rev[i >> 1] >> 1;
if (i & 1)
rev[i] |= lim >> 1;
// 或者合并写为
// rev[i] = (rev[i >> 1] >> 1) | ((i & 1) * lim_2);
}
现在我们可以写出非递归版的 FFT。
void FFT(Comp *f, int n, int type) {
for (int i = 0; i < n; ++i) {
if (i < rev[i]) {
swap(f[i], f[rev[i]]);
}
}
for (int h = 2; h <= n; h <<= 1) {
Comp step(cos(2 * PI / h), sin(2 * PI * type / h));
for (int j = 0; j < n; j += h) {
Comp cur(1, 0);
for (int k = j; k < j + h / 2; k++) {
Comp f1 = f[k], f2 = f[k + h / 2];
f[k] = f1 + cur * f2;
f[k + h / 2] = f1 - cur * f2;
cur = cur * step;
}
}
}
if (type == 1)
return;
for (int i = 0; i < n; i++)
f[i].x /= n, f[i].y /= n;
}
void FFT(Comp *f, int n, int type) {
for (int i = 0; i < n; ++i) {
if (i < rev[i]) {
swap(f[i], f[rev[i]]);
}
}
for (int h = 2; h <= n; h <<= 1) {
Comp step(cos(2 * PI / h), sin(2 * PI * type / h));
for (int j = 0; j < n; j += h) {
Comp cur(1, 0);
for (int k = j; k < j + h / 2; k++) {
Comp f1 = f[k], f2 = f[k + h / 2];
f[k] = f1 + cur * f2;
f[k + h / 2] = f1 - cur * f2;
cur = cur * step;
}
}
}
if (type == 1)
return;
for (int i = 0; i < n; i++)
f[i].x /= n, f[i].y /= n;
}
三步并做两步
实战一下:P3803 多项式乘法。
我们的计算步骤是:
FFT(F, lim, 1);
FFT(G, lim, 1);
for (int i = 0; i <= lim; i++)
F[i] = F[i] * G[i];
FFT(F, lim, -1);
FFT(F, lim, 1);
FFT(G, lim, 1);
for (int i = 0; i <= lim; i++)
F[i] = F[i] * G[i];
FFT(F, lim, -1);
实际上,我们并不用三次 FFT,两次足以。注意到若把
平方之后虚部恰是答案。
无 REV
实际上 REV 是可以消掉的,完全的推导比较复杂,这里只是以板子的形式给出。
template <class iter>
void ntt(iter f, int n) {
pre_w(n), ntt_size += n;
for (int l = n / 2; l; l >>= 1)
for (int i = 0; i < n; i += l * 2)
for (int j = 0; j < l; j++) {
int x = f[i + j], y = f[i + j + l];
f[i + j + l] = 1ll * (x - y + P) * w[j + l] % P;
f[i + j] = mo(x + y);
}
}
template <class iter>
void intt(iter f, int n) {
pre_w(n), ntt_size += n;
for (int l = 1; l < n; l <<= 1)
for (int i = 0; i < n; i += l * 2)
for (int j = 0; j < l; j++) {
int x = f[i + j];
int y = 1ll * w[j + l] * f[i + j + l] % P;
f[i + j] = mo(x + y), f[i + j + l] = mo(x - y + P);
}
const int iv = qpow(n);
for (int i = 0; i < n; i++)
f[i] = 1ll * f[i] * iv % P;
reverse(f + 1, f + n);
}
template <class iter>
void ntt(iter f, int n) {
pre_w(n), ntt_size += n;
for (int l = n / 2; l; l >>= 1)
for (int i = 0; i < n; i += l * 2)
for (int j = 0; j < l; j++) {
int x = f[i + j], y = f[i + j + l];
f[i + j + l] = 1ll * (x - y + P) * w[j + l] % P;
f[i + j] = mo(x + y);
}
}
template <class iter>
void intt(iter f, int n) {
pre_w(n), ntt_size += n;
for (int l = 1; l < n; l <<= 1)
for (int i = 0; i < n; i += l * 2)
for (int j = 0; j < l; j++) {
int x = f[i + j];
int y = 1ll * w[j + l] * f[i + j + l] % P;
f[i + j] = mo(x + y), f[i + j + l] = mo(x - y + P);
}
const int iv = qpow(n);
for (int i = 0; i < n; i++)
f[i] = 1ll * f[i] * iv % P;
reverse(f + 1, f + n);
}
应用
FFT 是卷积计算的基础工具。更多应用见 卷积。