【学习笔记】FFT

前置知识

  1. 复数

重要性质:复数相乘,辐角相加,模长相乘。

\(\operatorname{arg}(z_1\times z_2)=\operatorname{arg}(z_1)+\operatorname{arg}(z_2)\)

那么 \(\operatorname{arg}(z^n)=n\operatorname{arg}(z)\)

  1. 弧度制

  2. 三角函数

单位根

百度百科

\(n\) 次幂 为 \(1\) 的复数叫做 \(n\) 次单位根,即方程 \(z^n=1\) 的复数根。\(n\) 次单位根有 \(n\) 个。

因为 \(z^n=1\),所以 \(\left\vert z\right\vert^n=1\)。而模长一定是个非负实数,所以 \(\left\vert z\right\vert=1\)。也就是说单位根到原点的距离为 \(1\),一定在单位圆(以原点为圆心,半径为 \(1\) 单位长度的圆)上。

我们把所有 \(n\) 次单位根从 \(1\) 开始,沿逆时针方向记为 \(\omega_n^0,\omega_n^1,\dots\omega_n^{n-1}\)

由原方程还可以得到 \(\operatorname{arg}((\omega_n^k)^n)=n\operatorname{arg}(\omega_n^k)=\operatorname{arg}(1)\),所以把 \(1\) 逆时针旋转 \(n\) 次,每次旋转 \(\operatorname{arg}(\omega_n^k)\),结果还是 \(1\)。那么 \(2\pi\mid n\operatorname{arg}(\omega_n^k)\)。每个单位根又互不相同,所以 \(\operatorname{arg}(\omega_n^k)=\dfrac{k}{n}2\pi\)。它们把单位圆 \(n\) 等分。\(\omega_n^k\) 相当于绕原点把 \(\omega_n^{k-1}\) 逆时针旋转 \(\dfrac{2\pi}{n}\)

对于 \(k~(k\ge n)\),定义 \(\omega_n^k=\omega_n^{k\bmod n}\)

性质:

  1. \(\omega_n^k=(\omega_n^1)^k\)

  2. \(\omega_{nm}^{km}=\omega_n^k\)

因为它们模长都是 \(1\),辐角也相等。

  1. \(\omega_n^i\times\omega_n^j=\omega_n^{i+j}\)

用性质 1 容易得证。

  1. \(n\) 是偶数,\(\omega_n^{k+\frac{n}{2}}=-\omega_n^k\)

\(\omega_n^{k+\frac{n}{2}}=\omega_n^k\times\omega_n^\frac{n}{2}\)\(\operatorname{arg}(\omega_n^\frac{n}{2})=180^\circ\),相当于把 \(\omega_n^k\) 绕原点旋转 \(180^\circ\),即关于原点中心对称,实部虚部都取相反数,所以是 \(-\omega_n^k\)

  1. \(\sum\limits_{i=0}^{n-1}\omega_n^i=\dfrac{\omega_n^{n-1}\times\omega_n^1-\omega_n^0}{\omega_n^1-1}=0\)

(等比数列求和)

DFT

\(n+1\) 个点确定唯一 \(n\) 次多项式。我们可以用 \(n+1\) 个点表示原多项式,并对点值序列进行一些操作(如乘法),最后由这个点值序列得到一个新的多项式,来代替原多项式之间的操作。

比如 \(F(x)=x^2+2x-1,G(x)=-3x+1\)

\(F(x)\) 上取 \((-1,-2)(0,-1)(1,2)(2,7)\)

\(G(x)\) 上取 \((-1,4)(0,1)(1,-2)(2,-5)\)

两个点值序列中,横坐标相同的点的纵坐标相乘,得到 \((-1,-8)(0,-1)(1,-4)(2,-35)\)

根据这些点确定一个 \(3\) 次多项式得到 \(-3x^3-5x^2+5x-1\),恰好等于 \(F(x)\cdot G(x)\)

DFT 是把系数表达转换成点值表达的过程。我们把单位根代入多项式得到点值序列。

考虑一个 \(n-1\) 次(\(n\) 项)多项式 \(F(x)=\sum\limits_{i=0}^{n-1}F_ix^i\)

由于 \(n\)\(2\) 的整数幂时比较好做,故 \(n\) 不是 \(2\) 的整数幂时往高次项补 \(0\)

把它按次数的奇偶性分类:

\(F(x)=F_0+F_2x^2+\dots+F_{n-2}x^{n-2}+F_1x+F_3x^3+\dots+F_{n-1}x^{n-1}\)

\(FL(x)=F_0+F_2x+F_4x^2+\dots+F_{n-2}x^{n/2-1},FR(x)=F_1+F_3x+F_5x^2+\dots+F_{n-1}x^{n/2-1}\)

则有 \(F(x)=FL(x^2)+xFR(x^2)\)

现要把单位根代入 \(F\)

枚举 \(k~(0\le k<\dfrac{n}{2})\)

\(F(\omega_n^k)=FL((\omega_n^k)^2)+\omega_n^kFR((\omega_n^k)^2)=FL(\omega_{n/2}^k)+\omega_n^kFR(\omega_{n/2}^k)\)

\(F(\omega_n^{n/2+k})=FL((\omega_n^{n/2+k})^2)+\omega_n^{n/2+k}FR((\omega_n^{n/2+k})^2)=FL(\omega_n^{n+2k})+\omega_n^{n/2+k}FR(\omega_n^{n+2k})=FL(\omega_{n/2}^k)-\omega_n^kFR(\omega_{n/2}^k)\)

如果我们知道了 \(x=\omega_{n/2}^k~(0\le k<n/2)\)\(FL(x)\)\(FR(x)\) 的值,就可以算出 \(x=\omega_n^k~(0\le k<n)\)\(F(x)\) 的值。\(FL,FR\) 可以递归求。

求单位根:用三角函数求出 \(\omega_n^1\),利用性质 1 每次乘上它,防止多次调用三角函数。

\(\large{F(x)=FL(x^2)+xFR(x^2)}\\\large{F(\omega_n^k)=FL(\omega_{n/2}^k)+\omega_n^kFR(\omega_{n/2}^k)}\\\large{F(\omega_n^{n/2+k})=FL(\omega_{n/2}^k)-\omega_n^kFR(\omega_{n/2}^k)}\)

IDFT

有了点值序列还不够,我们还得根据点值求多项式。

\(G\) 为点值序列即 \(G_k=F(\omega_n^k)=\sum\limits_{i=0}^{n-1}F_i(\omega_n^k)^i\)

\(nF_k=\sum\limits_{i=0}^{n-1}(\omega_n^{-k})^iG_i\)

也就是说,对 \(G\) 再做一遍 DFT,不同的是初始单位根为 \(1\),每次乘上 \(\omega_n^{-1}\)

\(\omega_n^{-1}\)\(\omega_n^{1}\) 的共轭。

不会证

代码实现

根据上文可以写出递归的版本,但常数较大。现在尝试写出非递归版。

考虑 \(0\dots n-1\) 次项系数递归到每一层时的位置。

\(n=8\) 为例

0 1 2 3 4 5 6 7
---------------
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

写出第一行和最后一行的二进制

|   | 原  | 现  |
| 0 | 000 | 000 |
| 1 | 001 | 100 |
| 2 | 010 | 010 |
| 3 | 011 | 110 |
| 4 | 100 | 001 |
| 5 | 101 | 101 |
| 6 | 110 | 011 |
| 7 | 111 | 111 |

我们发现,最后一行 \(i\) 位置上的数其实是位数为 \(\log n\)\(i\) 在二进制下反转得到的数。 于是可以预处理出 \(i\) 在二进制下反转得到的数,也就能得到最后一行。此时他们都是 \(0\) 次,可以直接当做单位根代入得到的值。

然后枚举现在要处理 \(m\) 次多项式(\(m=2,4,8,.\dots,n\))。把序列分成 \(\dfrac{n}{m}\) 段,枚举段的起始点 \(l\),在这一段内枚举 \(k~(0\le k<\dfrac{m}{2})\) 计算 \(F(l+k)\)\(F(l+\dfrac{m}{2}+k)\).

代码(多项式乘法):

const double pi = acos(-1);
int tr[N], n, m;

struct CP {
	double x, y;

	inline CP operator + (const CP &c) const { return CP(x + c.x, y + c.y); }
	inline CP operator - (const CP &c) const { return CP(x - c.x, y - c.y); }
	inline CP operator * (const CP &c) const { return CP(x * c.x - y * c.y, x * c.y + y * c.x); }

	CP() {}
	CP(const double &_x, const double &_y) : x(_x), y(_y) {}

} f[N], g[N];

inline void swap(CP &x, CP &y) {
	CP t;
	t = x;
	x = y;
	y = t;
}

void FFT(CP *f, int fl) { // fl=1 DFT ; fl=-1 IDFT
	CP w1, buf, tmp;
	int now;
	rep(i, 0, n - 1) if(i < tr[i]) swap(f[i], f[tr[i]]); // 得到最后一层
	for(rei lim = 2; lim <= n; lim <<= 1) { //上文中的 m
		now = lim >> 1;
		w1 = CP(cos(pi * 2 / lim), fl * sin(pi * 2 / lim)); //w(lim,1)
		for(rei i = 0; i < n; i += lim) { //上文中的 l
			buf = CP(1, 0);
			rep(j, i, i + now - 1) { // 上文中的 l+k
				tmp = buf * f[j + now];
				f[j + now] = f[j] - tmp;
				f[j] = f[j] + tmp;
				buf = buf * w1;
			}
		}
	}
}

signed main() {
	n = read(); m = read();
	rep(i, 0, n) f[i].x = read();
	rep(i, 0, m) g[i].x = read();
	for(m += n, n = 1; n <= m; n <<= 1) ;
	rep(i, 0, n - 1) tr[i] = (tr[i >> 1] >> 1) | (i & 1 ? (n >> 1) : 0); // 预处理二进制反转的结果
	FFT(f, 1);
	FFT(g, 1);
	rep(i, 0, n - 1) f[i] = f[i] * g[i];
	FFT(f, -1);
	rep(i, 0, m) printf("%d ", (int) (f[i].x / n + 0.49));
	putchar(10);
	return 0;
}
posted @ 2021-03-26 10:21  EverlastingEternity  阅读(151)  评论(0编辑  收藏  举报