快速傅里叶变换
0.用处
用来将多项式乘法,也就是卷积,从 优化到 。
FFT 是 DFT(离散傅里叶变换)的优化,用来系数表示法转点值表示法,IFFT 是 IDFT 的优化,作用相反。
什么是点值表示法
因为一个多项式 ,如果我们选 个 带入,则可以列出一个关于 的 元一次方程组,就可以解出每一个 。
因此,点集 就可以唯一确定一个多项式。
而卷积用点值表示法可以这么写:
后一个式子就是朴素的复数乘法。
傅里叶变换有个性质:,所以可以用它来优化。
1.前置知识
复数
定义 & 运算
形如 的数为复数,,为实部,,为虚部, 为虚数单位,。复数集为 ,加减法就是实部虚部对应相加/减。
乘法定义如下: 。
实质就是模长相乘,辐角相加,因为 ,所以
给出封装的代码模板:
CPP
12345678
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,最后答案要除以 。为什么是这样?
这个东西乍一看很烦,实则确实。建议使用全文背诵法记忆
模板代码
CPP
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
#include <bits/stdc++.h>
#define int long long
using 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。