0.用处
用来将多项式乘法,也就是卷积,从 优化到 。
FFT 是 DFT(离散傅里叶变换)的优化,用来系数表示法转点值表示法,IFFT 是 IDFT 的优化,作用相反。
什么是点值表示法
因为一个多项式 ,如果我们选 个 带入,则可以列出一个关于 的 元一次方程组,就可以解出每一个 。
因此,点集 就可以唯一确定一个多项式。
而卷积用点值表示法可以这么写:
后一个式子就是朴素的复数乘法。
傅里叶变换有个性质:,所以可以用它来优化。
1.前置知识
复数
定义 & 运算
形如 的数为复数,,为实部,,为虚部, 为虚数单位,。复数集为 ,加减法就是实部虚部对应相加/减。
乘法定义如下: 。
实质就是模长相乘,辐角相加,因为 ,所以
给出封装的代码模板:
struct Complex{ double re,im; Complex(double re,double im):re(re),im(im){}; Complex operator+(const Complex &a) const {return Complex(re + a.re,im + a.im);} Complex operator-(const Complex &a) const {return Complex(re - a.re,im - a.im);} Complex operator*(const Complex &a) const {return Complex(re * a.re - im * a.im,re * a.im + im * a.re);}};单位根
满足 的复数 称为 次单位根。
记 为第 个 次单位根。
则有性质:
2. 正文
DFT
其实就是点值表示法中,。
FFT
在 DFT 的基础上,用计算机的办法优化。
对于一个 次多项式 ,我们可以按指数的奇偶性来给每一项分类,即
为了方便,我们记 。
显然有 。
带入 ,有 。
所以,可以递归求解,当 时结束。
IFFT
就是上边的逆变换。具体操作就是带入的单位根变为 ,即原单位根的倒数,然后再做一次 FFT,最后答案要除以 。为什么是这样?↗
这个东西乍一看很烦,实则确实。建议使用全文背诵法记忆
模板代码
#include <bits/stdc++.h>#define int long longusing namespace std;
constexpr int MAXN = 4e6 + 24;const double Pi = acos(-1.0);struct Complex{ double re,im; Complex(double re = 0.0,double im = 0.0):re(re),im(im){}; Complex operator+(const Complex &a) const {return Complex(re + a.re,im + a.im);} Complex operator-(const Complex &a) const {return Complex(re - a.re,im - a.im);} Complex operator*(const Complex &a) const {return Complex(re * a.re - im * a.im,re * a.im + im * a.re);}};void FFT(int limit,Complex *a,int type){ if(limit == 1) return ; Complex a1[limit >> 1],a2[limit >> 1]; for(int i = 0;i < limit;i += 2) a1[i >> 1] = a[i],a2[i >> 1] = a[i + 1]; FFT(limit >> 1,a1,type); FFT(limit >> 1,a2,type); Complex omega = Complex(cos(2.0 * Pi / limit),type * sin(2.0 * Pi / limit)),Pow = Complex(1,0); for(int i = 0;i < (limit >> 1);i++,Pow = Pow * omega) { a[i] = a1[i] + Pow * a2[i]; a[i + (limit >> 1)] = a1[i] - Pow * a2[i]; }}int n,m;Complex a[MAXN],b[MAXN];signed main(){ ios::sync_with_stdio(0),cin.tie(0),cout.tie(0); cin >> n >> m; for(int i = 0;i <= n;i++) cin >> a[i].re; for(int i = 0;i <= m;i++) cin >> b[i].re; int limit = 1; while(limit <= n + m) limit <<= 1; FFT(limit,a,1); FFT(limit,b,1); for(int i = 0;i <= limit;i++) a[i] = a[i] * b[i]; FFT(limit,a,-1); for(int i = 0;i <= n + m;i++) cout << (int)(a[i].re / limit + 0.5) << " ";}注意,这个 一般用 acos(-1) 来求最精确,但如果你要背的话一定要写 ,少了会 WA。