FFT 学习笔记
FFT 学习笔记
\(\mathbf{Preview}\)
前置知识。
多项式表示法
系数表示法:就是正常的多项式表示方法。\(f(x) = \sum\limits_{i = 0}^{n - 1} a_i \times x^i\)。
点值表示法:如果我们知道 \(n\) 个数 \(b_1, \cdots, b_n\),这些数在带入 \(f\) 多项式后他们的具体值 \(f(b_1), \cdots, f(b_n)\),我们就可以通过这些值算出 \(f\),因此 \(f\) 也可以用若干个点对 \(\Big(b_1, f(b_1)\Big),\cdots,\Big(b_n, f(b_n)\Big)\) 来表示。1
向量
向量是一个有方向,有长度的量,为了方便运算,起点坐标通常被设为原点。
但正如同刚才所说,向量的两个关键因素为方向和长度,所以还可以用这一种方式:
\]
这也被称为 极角坐标系, \(r\) 也被称为向量的模长,\(\alpha\) 自然是与 \(x\) 轴成的夹角。
向量在坐标系中大致是这样:

由于在 FFT 中不是很重要,所以粗略介绍。
弧度制
一个点在一个以原点为中心半径为 \(1\) 的圆上时,这个点与 \(x\) 轴的夹角在弧度制下被表示为这个点到圆与 \(x\) 轴的交点的圆弧长度。简单来说,\(2\pi = 360\degree\)。
复数
设 \(a,b\) 为实数,\(i^2 = -1\),则形如 \(a+bi\) 的数被称为复数。
在复平面中,\(x\) 轴表示实数,\(y\) 轴(除原点)表示虚数,从 \((0, 0)\to (a, b)\) 的向量表示 \(a+bi\)。
模长:表示 \(a+bi\) 向量的模长,即 \(\sqrt{a^2 + b^2}\)。
幅角:以逆时针为正方向,\(x\) 轴正方向到 \(a+bi\) 的向量形成的有向角。
加法运算:\((a+bi)+(c+di) = (a+c)+(b+d)i\)
减法运算:\((a+bi)-(c+di) = (a-c)+(b-d)i\)
乘法运算:
- 几何定义:复数相乘,模长相乘,幅角相加
- 代数定义:\((a+bi)\times (c+di) = (ac - bd) + (bc + ad)i\)。
除法定义在本文中不提及。
单位根
下文中,默认 \(n\) 为 \(2\) 的正整数次幂
以单位圆原点为起点,圆的 \(n\) 等分点为终点,做 \(n\) 个向量,设幅角为正且最小的向量对应的复数为 \(w_n\),称为 \(n\) 次单位根。
根据复数乘法的运算法则,其余 \(n - 1\) 个复数为 \(w_n^2, w_n^3,\cdots,w_n^{n}\)
注意 \(w_n^0 = w_n^n\)(对应复平面上以 \(x\) 轴为正方向的向量,就是图中的 \(u\))
由欧拉公式,\(w_n^k = \cos k\dfrac{2\pi}{n}+i\sin{k\dfrac{2\pi}{n}}\),且有 \(w_{2n}^{2k} = w_n^k,w_{n}^{k+\frac{n}{2}}=-w_{n}^k,w_n^0 = w_n^n = 1\)。
接下来进入正题。
\(\mathbf{Part. 0 \ \ 思路}\)
首先,我们考虑如何计算两个多项式 \(P, Q\) 的乘法,假设次数分别为 \(n - 1, m - 1\),为了方便后面的分治,我们先把 \(P, Q\) 的长度扩成一个大于 \(n + m\) 的 \(2\) 的幂次。设这个幂次为 \(l = 2^t\)。
显然,在系数表示法中,我们只能 \(\mathcal{O}(n^2)\) 求乘法。
但是,如果我们能把 \(P, Q\) 都转化成点值表示法,其中 \(P\) 的点值表示法为 \(\Big(x_1, P(x_1)\Big),\cdots,\Big(x_l, P(x_l)\Big)\),\(Q\) 的点值表示法为 \(\Big(x_1, Q(x_1)\Big),\cdots,\Big(x_l, Q(x_l)\Big)\)。然后,因为 \(P(x_i)\) 和 \(Q(x_i)\) 是两个多项式的结果,所以我们可以直接对每个 \(P(x_i) \times Q(x_i) = Z(x_i)\)。对于 \(\Big(x_1, Z(x_1)\Big),\cdots,\Big(x_l, Z(x_l)\Big)\),我们将点值表示法转化回系数表示法,就能得到 \(P \times Q\) 的值。
整合思路,我们需要将系数表示法转化为点值表示法,再将点值表示法转化回系数表示法。
第一步被称为 \(\mathbf{FFT}\),第二部被称为 \(\mathbf{IFFT}\)。
\(\mathbf{Part. 1 \ \ FFT}\)
\(\mathbf{FFT}\) 是为了解决如何快速将一个多项式从系数表示法转化为多项式表示法的。如果我们直接暴力计算,我们要对于 \(n\) 个数,每个数计算 \(f(x)\),总复杂度 \(\mathcal{O}(n^2)\)。(相关章节:\(\mathbf{Preview},多项式表示法\))
设我们现在想把 \(A(x) = \sum\limits_{i = 0}^{n - 1} a_i\times x^i\) 转化为点值表示法。
在 \(\mathbf{Preview}\) 中的 多项式表示法 中,由 <1>,我们知道一个多项式可以被 \(n\) 个点唯一确定。
假设最终的点值表示法是 \(\Big(x_1, f(x_1)\Big),\cdots,\Big(x_n, f(x_n)\Big)\),我们考虑将 \(x_1, \cdots, x_n\) 设为 \(w_n^0, \cdots, w_n^{n - 1}\),然后计算 \(f(w_n^0),\cdots,f(w_n^{n - 1})\)。所以我们只需要计算 \(A(w_n^0),\cdots,A(w_n^{n - 1})\)。2
考虑分治计算这个值。我们把 \(A(x)\) 按照下标奇偶性进行分类:
A(x) = & (a_0 + a_2 \times x^2 + \cdots + a_{n - 2} \times x^{n-2}) +\\
& (a_1 \times x^1 + a_3\times x^3 + \cdots + a_{n - 1}\times x^{n - 1})
\end{aligned}
\]
然后继续考虑构造 \(A_1, A_2\):
A_2(x) = a_1 + a_3 \times x + \cdots + a_{n - 1} \times x ^ {\frac{n}{2} - 1}
\]
那么综合 \((2)(3)\):
\]
由 <2> 知,我们需要把 \(w_n^k\) 代入多项式。
由于我们需要分治,考虑将 \(k\) 按照 \(k < \dfrac{n}{2}\) 和 \(k \geq \dfrac{n}{2}\) 考虑。
对于 \(k < \dfrac{n}{2}\):(接下来的化简如果不懂,可以尝试结合 \(\mathbf{Preview}\) 中的 单位根 来看)
A(w_n^k) & = A_1(w_n^{2k}) + w_n^kA_2(w_n^{2k})\\
& = A_1(w_{\frac{n}{2}}^k) + w_n^kA_2(w_{\frac{n}{2}}^k)
\end{aligned}
\]
对于 \(k\geq \dfrac{n}{2}\),考虑 \(k = k' + \dfrac{n}{2}\):
A(w_n^{k}) & = A_1(w_n^{2k' + n}) + w_n^{k' + \frac{n}{2}} A_2(w_{n}^{2k' + n})\\
& = A_1(w_n^{2k'} \times w_n^n) + w_n^{k'} w_n^{\frac{n}{2}} A_2(w_n^{2k'} \times w_n^n)\\
& = A_1(w_n^{2k'}) - w_n^{k'} A_2(w_n^{2k'})\\
& = A_1(w_{\frac{n}{2}}^{k'}) - w_n^{k'} A_2(w_{\frac{n}{2}}^{k'})
\end{aligned}
\]
我们发现,对于 \((5)\) 和 \((6)\) 的结果式,它们再 \(A_1\) 和 \(A_2\) 多项式中代入得值是一样的(都是 \(w_{\frac{n}{2}}^k\),其中 \(k < \frac{n}{2}\))。因此,如果我们分治求出来了 \(A_1(w_{\frac{n}{2}}^{k})\) 和 \(A_2(w_{\frac{n}{2}}^k)\),我们就可以通过 \((5)(6)\) 来算出来 \(k < \dfrac{n}{2}, A(w_n^k)\) 和 \(k \geq \dfrac{n}{2},A(w_n^{k})\),因此拼起来就是完整的 \(A(w_n^k)\)。
因此,我们只需要分治求出来 \(A_1(w_{\frac{n}{2}}^{k})\) 和 \(A_2(w_{\frac{n}{2}}^k)\),然后求出来 \(A(w_n^k)\),我们就可以得出 \(A\) 的点值表示法。对于每一层,我们只需要 \(\mathcal{O}(n)\) 的时间复杂度计算,总共 \(\mathcal{O}(\log n)\) 层,因此时间复杂度为 \(\mathcal{O}(n\log n)\)。
\(\mathbf{Part. 2 \ \ IFFT}\)
\(\mathbf{IFFT}\) 是为了解决如何快速将一个多项式从点值表示法转化为系数表示法的。(相关章节:\(\mathbf{Preview},多项式表示法\))
假设我们对于多项式 \(A(x) = \sum\limits_{i = 0}^{n - 1} a_i \times x^i\),求出来了 \(A\) 的点值表示 \(\Big(w_n^0, t_0\Big), \cdots, \Big(w_n^{n - 1}, t_{n - 1}\Big)\),则我们想要通过 \(t\) 数组来还原出 \(a\) 数组。
我们考虑构造数组 \(c\),使得 \(c_k = \sum\limits_{i = 0}^{n - 1} t_i \times(w_n^{-k})^i\)。这个式子的含义是,我们考虑对于 \(f(x) = \sum\limits_{i = 0}^{n - 1}t_i \times x ^ i\) 这个多项式,代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 计算 \(f(x)\)(也就是 \(t\) 数组)的点值表示法,得到 \(c_0, \cdots, c_{n - 1}\)。3
这里你可以这样理解:我们在对 \(A\) 求出代入 \((w_n^0, \cdots, w_n^{n - 1})\) 时的点值表示时,再对这个结果代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 时求出点值表示,最后得到 \(c\) 数组。
考虑对这个 \(c\) 数组进行变换:
c_k & = \sum_{i = 0}^{n - 1} t_i \times(w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1} \color{red}A(w_n^{i})\color{black}\times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1} \color{red}\sum_{j = 0}^{n - 1} a_j \times (w_n^i)^j\color{black}\times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times (w_n\color{red}^j\color{black})\color{red}^i\color{black} \times (w_n^{-k})^i\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times \color{red}(\color{black}w_n^j\times w_n^{-k}\color{red})^i\color{black}\\
& = \sum_{i = 0}^{n - 1}\sum_{j = 0}^{n - 1}a_j \times (\color{red}w_n^{j - k}\color{black})^i\\
& = \color{red}\sum_{j = 0}^{n - 1}a_j \times \sum_{i = 0}^{n - 1}\color{black}(w_n^{j - k})^i
\end{aligned}
\]
我们考虑 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i\) 如何快速计算。容易发现这是一个等比数列的形式,则:
- \(j - k = 0\),则 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i = \sum\limits_{i = 0}^{n - 1}1^i = n\)。
- \(j = k \neq 0\),则由于等比数列的比为 \(w_{n}^{j - k}\),则 \(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i = \dfrac{(w_n^{j - k})^n - (w_n^{j - k})^0}{w_{n}^{j - k} - 1} = 0\)。
因此,我们发现,只有当 \(j = k\) 时,\(\sum\limits_{i = 0}^{n - 1}(w_n^{j - k})^i\) 为 \(n\),其余情况均为 \(0\)。因此,\((21)\) 结果式转化为:
\]
因此,\(a_k = c_k \times \dfrac{1}{n}\),而 \(a_k\) 就是我们想求的值!
考虑 \(c_k\) 是什么,由 <3>,我们知道 \(c_k\) 是对于 \(f(x) = \sum\limits_{i = 0}^{n - 1}t_i \times x ^ i\) 这个多项式,代入 \((w_n^0, \cdots, w_n^{-(n - 1)})\) 计算 \(f(x)\)(也就是 \(t\) 数组)的点值表示法。因此,我们只需要对 \(t_0, \cdots, t_{n - 1}\) 求出来点值表示即可,这个和上面 \(\mathbf{Part. 1 \ \ FFT}\) 唯一的不同就是代入值中单位根的次数一个为正,一个为负。
所以,\(\mathbf{IFFT}\) 实际上就是对 \(t\) 数组再做一遍 \(\mathbf{FFT}\),然后对于每个 \(c_k\) 乘上 \(\dfrac{1}{n}\) 就是 \(a_k\)。
\(\mathbf{Part. +\infin \ \ Details}\)
/*******************************
| Author: DE_aemmprty
| Problem: P3803 【模板】多项式乘法(FFT)
| Contest: Luogu
| URL: https://www.luogu.com.cn/problem/P3803
| When: 2025-07-26 15:03:20
|
| Memory: 500 MB
| Time: 2000 ms
*******************************/
#include <bits/stdc++.h>
using namespace std;
int read() {
char c = getchar(); int x = 0;
while ((c < '0' || c > '9') && c != '-') c = getchar();
while (c >= '0' && c <= '9') x = (x << 1) + (x << 3) + (c ^ 48), c = getchar();
return x;
}
const int N = 3e6 + 107;
const long double pi = acos(-1);
int n, m, id[N], l;
struct complexor {
double x, y;
complexor (double p = 0, double q = 0) : x(p), y(q) {}
complexor operator + (const complexor &t) const { return complexor(x + t.x, y + t.y); }
complexor operator - (const complexor &t) const { return complexor(x - t.x, y - t.y); }
complexor operator * (const complexor &t) const {
return complexor(x * t.x - y * t.y, x * t.y + y * t.x);
}
} p[N];
complexor omega(int n, int k) { return complexor(cos(pi * 2.0 * k / n), sin(pi * 2.0 * k / n)); }
void FFT(complexor *a, int op) {
for (int i = 0; i < l; i ++)
if (i < id[i]) swap(a[i], a[id[i]]);
for (int d = 1; d < l; d <<= 1) {
complexor w = omega((d << 1), op), W = 1;
for (int k = 0; k < d; k ++) {
for (int i = 0; i < l; i += (d << 1)) {
complexor at = a[i + k], bt = W * a[i + d + k];
a[i + k] = at + bt;
a[i + d + k] = at - bt;
}
W = W * w;
}
}
}
void solve() {
n = read(), m = read();
for (int i = 0; i <= n; i ++) p[i].x = read();
for (int i = 0; i <= m; i ++) p[i].y = read();
l = 1; while (l <= n + m) l <<= 1;
for (int i = 0; i < l; i ++)
id[i] = (id[i >> 1] >> 1) | ((i & 1) * (l >> 1));
FFT(p, 1);
for (int i = 0; i < l; i ++)
p[i] = p[i] * p[i];
FFT(p, -1);
for (int i = 0; i < n + m + 1; i ++)
cout << (int) (p[i].y / l / 2.0 + 0.5) << ' ';
cout << '\n';
}
signed main() {
int t = 1;
// t = read();
while (t --) solve();
return 0;
}
\(\mathbf{Question. 1}\)
第 \(42, 43\) 行的
swap是什么意思?(为什么不是递归写法?)
在 \(\mathbf{Part.1 \ \ FFT}\) 中,我们对每个多项式,按照奇偶分治。但是,我们在 \(\mathbf{FFT}\) 的过程中,如果递归的去做,我们还要在计算的过程中将偶数项排在前面,奇数项排在后面,这样的常数我们是不能接受的。那能不能快速的找到一种排序方式,直接把最终的顺序找出来,这样我能不用一边递归一边排序,而是一开始就排好序?
观察结构,你发现题目对于每个 \(i\),首先按照二进制第一位排序,其次按照二进制第二位排序……实际上,\(\mathbf{FFT}\) 的过程是在对每个 \(i\),它的二进制从低位到高位进行排序,也就是按照二进制的翻转串排序。(注意,这里的翻转指第 \(1\) 位翻转到第 \(n\) 位,而不是 \(0\to1,1\to0\))
所以第 \(i\) 个数的排名其实就是 \(i\) 的二进制翻转串的值。(如假设总长 \(l = 8\),\(i = 3 = (011)_2\),那么 \(i\) 的翻转串就是 \((011)_2 \to (110)_2\)。又由于最终序列按照翻转串来排序,所以 \(i\) 的排名就是 \((110)_2 = 6\))
那如何 \(\mathcal{O}(n)\) 求出来第 \(i\) 个数的翻转串的值用于求排名?
我们考虑递推。设 \(f_i\) 表示第 \(i\) 个数的翻转串的值,则我们考虑 比较 \(i\) 与 \(i' = i \gg 1\) 的关系。假设 \(i = (011)_2,i' = (001)_2\),那我们考虑两个数的翻转串分别为 \((110)_2, (100)_2\)。容易发现,由于 \(i\) 是 \(i’\) 左移一位加上 \((i \,\&\, 1)\),所以 \(i\) 的翻转串是 \(i'\) 右移一位加上 \((i\,\&\,1)\) 乘上最高位的值。因此,\(f_x = (f_{x \gg 1} \gg 1) \, \big| \, \Big((x \, \& \, 1) \times mx\Big)\),其中 \(mx\) 为最高位。
\(\mathbf{Question. 2}\)
为什么
swap之后,\(\mathbf{FFT}\) 求出来的值顺序不会乱呢?
考虑回顾 \(\mathbf{Part.1 \ \ FFT}\) 的内容。我们对于奇偶的下标,分别求出了 \(A_1(w_{\frac{n}{2}}^{i})\) 和 \(A_2(w_{\frac{n}{2}}^i)\) 的值,然后我们按顺序求出了 \(A(w_n^i)\) 的值并继续往上递归。我们这里要注意不能混淆:我们确实打乱了 \(A\) 多项式的系数表示的顺序,但是我们求出来的是点值表示,这个点值表示还是按照单位根的顺序求出来的。所以 swap 之后 \(A\) 多项式存的点值表示是按顺序的,没有影响。
FFT 学习笔记的更多相关文章
- 快速傅里叶变换(FFT)学习笔记
定义 多项式 系数表示法 设\(A(x)\)表示一个\(n-1\)次多项式,则所有项的系数组成的\(n\)维向量\((a_0,a_1,a_2,\dots,a_{n-1})\)唯一确定了这个多项式. 即 ...
- 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)
再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...
- 快速傅里叶变换(FFT)学习笔记(其一)
再探快速傅里叶变换(FFT)学习笔记(其一) 目录 再探快速傅里叶变换(FFT)学习笔记(其一) 写在前面 为什么写这篇博客 一些约定 前置知识 多项式卷积 多项式的系数表达式和点值表达式 单位根及其 ...
- 快速傅里叶变换(FFT)学习笔记(其二)(NTT)
再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...
- 口胡FFT现场(没准就听懂了)&&FFT学习笔记
前言(不想听的可以跳到下面) OK.蒟蒻又来口胡了. 自从ZJOI2019上Day的数论课上的多项式听到懵逼了,所以我就下定决心要学好多项式.感觉自己以前学的多项式都是假的. 但是一直在咕咕,现在是中 ...
- 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w
现在真是一碰电脑就很颓废啊... 于是早晨把电脑锁上然后在旁边啃了一节课多的算导, 把FFT的基本原理整明白了.. 但是我并不觉得自己能讲明白... Fast Fourier Transformati ...
- FFT学习笔记
快速傅里叶变换FFT(Fast Fourior Transform) 先说一下它能干嘛qwq 傅里叶变换有两种,连续傅里叶变换和离散傅里叶变换,OI中主要用来快速计算多项式卷积. 等一下,卷积是啥 ...
- 多项式乘法(FFT)学习笔记
------------------------------------------本文只探讨多项式乘法(FFT)在信息学中的应用如有错误或不明欢迎指出或提问,在此不胜感激 多项式 1.系数表示法 ...
- 【文文殿下】快速傅里叶变换(FFT)学习笔记
多项式 定义 形如\(A(x)=\sum_{i=0}^{n-1} a_i x^i\)的式子称为多项式. 我们把\(n\)称为该多项式的次数界. 显然,一个\(n-1\)次多项式的次数界为\(n\). ...
- 分治 FFT学习笔记
先给一道luogu板子题:P4721 [模板]分治 FFT 今天模拟有道题的部分分做法是分治fft,于是就学了一下.感觉不是很难,国赛上如果推出式子的话应该能写出来. 分治fft用来解决这么一个式子\ ...
随机推荐
- HarmonyOS NEXT开发实战教程:聊天交友App
一早醒来Mate70上热搜了,余承东发文宣布Mate70要在本月发布,史上最强手机终于要来了. 今天分享一个交友app实战教程,是幽蓝君用整整一个周末开发的,时间有限,只做了些皮毛,不是很完善,不过拿 ...
- 消息队列, 一种取舍的选择 Redis Stream
人多公司方便多个业务方解耦, 常用一些成熟的消息队列. 会有专门部门帮你维护好. 但在小公司, 看成本靠个人. 有的简单可能就是 redis list or mysql 存一些状态, 有问题了就自己手 ...
- L1-8、Prompt提升提问质量的实用技巧(Tips)
掌握提问的艺术,让你的 AI 更聪明.更贴心. 为什么你的提问得不到好答案? ChatGPT 等大模型的表现很大程度上取决于你的提问方式.提得好,AI 像专家:提不好,AI 像糊涂蛋. 常见低质量提问 ...
- 【深入理解Base64编码】原理、应用与Java实战
深入理解Base64编码:原理.应用与Java实战 目录 1-什么是base64编码 2-base64编码原理详解 3-base64的java实现 4-高级应用与技巧 5-注意事项与常见误区 6-总结 ...
- CAN304 W3
CAN304 W3 Message authentication code Message integrity 我们一直关注确保通信的保密性. Integrity:确保接收到的消息来自预期方,并且未被 ...
- 关于vue关闭页面时去除定时器失效问题解决
1.先去除页面缓存,这个在路由部分 2.
- ArkUI-X在Android上使用Fragment开发指南
本文介绍将ArkUI框架的UIAbility跨平台部署至Android平台Fragment的使用说明,实现Android原生Fragment和ArkUI跨平台Fragment的混合开发,方便开发者灵活 ...
- AI智能文案助手ChatMoney:一键打造抖音爆款视频,助你轻松吸引千万级流量!
本文由 ChatMoney团队出品 引言 看着抖音上别人的视频轻松破百万点赞,是不是心里痒痒的?想知道他们是怎么做到的?其实,他们可能只是比您先一步掌握了这个秘密武器--ChatMoney.这不仅仅是 ...
- JIT 编译优化原理深度解析
在 Java 高级程序员面试中,JIT(即时编译,Just-In-Time Compilation)作为提升程序执行效率的核心技术,是 JVM 原理模块的高频考点.本文从 JIT 架构设计.热点代码识 ...
- 数栈产品分享:简析数据中台如何通过DataAPI实现数据共享
数栈是云原生-站式数据中台PaaS,我们在github和gitee上有一个有趣的开源项目:FlinkX,FlinkX是一个基于Flink的批流统一的数据同步工具,既可以采集静态的数据,也可以采集实时变 ...