FFT——快速傅里叶变换
FFT——快速傅里叶变换
你也可以理解为
现在,我们要求
这样的时间复杂度明显不尽人意,那么
单位根的性质
-
对于任意
n ,ω_n^0=1 。 -
-
(ω_n^m)^k=(ω_n^k)^m -
-
-
若
2 \mid n ,ω_n^{m+\frac{n}{2}}=-ω_n^m
多项式的表达方法
-
系数表达法:这个如同我们上面写的多项式即为系数表达法。
-
点值表达法
定义多项式
我们知道,
这便是
多项式
可见,点值表达法下,多项式乘法是
- (补充)乘法本质:卷积
根据我们上面暴力计算
那么形如
那么,多项式的乘法就是对于加法的卷积。
我们仍然可以对多项式相乘的模板打个暴力:
#include<bits/stdc++.h>
using namespace std;
const int SIZE = 1e6 + 10;
long long A[SIZE], B[SIZE], C[SIZE];
int n, m;
int main() {
scanf("%d %d", &n, &m);
n++, m++;
for (int i = 0; i < n; i++) scanf("%lld", A + i);
for (int i = 0; i < m; i++) scanf("%lld", B + i);
for (int k = 0; k < n+m-1; k++)
for (int i = 0; i <= k; i++)
C[k] += A[i] * B[k - i];
for (int i = 0; i< n + m - 1; i++)
printf("%lld ", C[i]);
return 0;
}
模板题亲测 44 分。
神仙问题:tql but 这有用吗
当然有用啊:
算法流程:
这不就有个用了吗?
但这个思路仅仅是
如果让我们求点值表达式,那我们肯定会带整数进去,因为这样好计算。但是傅里叶,(不知道是不是脑抽了),把复数带进了多项式中,然后神奇的事情就发生了……
我们把多项式如下拆开:
(保证
然后设两个有
设
接着,就有
那么,如果我们知道
接下来我们的目标就是求
然后……
继续设
运用一系列单位根的性质,读者可以自行证明出:
然后我们就可以求出听着好累的样子……)
但是我们还不知道
但是我们想想:好像完成了一半了?
然后,因为
2. F(ω^{k+n/2}_{n})=FL(ω^{k}_{n/2})-ω^{k}_{n}FR(ω^{k}_{n/2})
那么,我们只要知道
怎么求
解三角形不就好了?已知
(由于
设幅角
这边要牢记,
struct complex { //复数
long double a, b;
complex() { a = b = 0;}
//重载复数四则运算
complex operator + (const complex x) const {
complex temp;
temp.a = a + x.a;
temp.b = b + x.b;
return temp;
}
complex operator - (const complex x) const {
complex temp;
temp.a = a - x.a;
temp.b = b - x.b;
return temp;
}
complex operator * (const complex x) const {
complex temp;
temp.a = a * x.a - b * x.b;
temp.b = a * x.b + b * x.a;
return temp;
}
complex operator *= (const complex &x) {
*this = *this * x;
return *this;
}
complex operator / (const complex x) const {
double div = x.a * x.a + x.b * x.b;
complex temp;
temp.a = (a * x.a + b * x.b) / div;
temp.b = (b * x.a - a * x.b) / div;
return temp;
}
}w[SIZE],temp, buf;
void unit_roots(int n) {
const long double pi = 3.14159265358979;
//complex ;
temp.a = cos(2*pi/n), temp.b = sin(2*pi/n);
w[0].a = 1, w[0].b = 0;
buf.a = 1, buf.b = 0;
for(int i = 1; i < n ; i++) {
buf *= temp;
w[i] = buf;
}
for(int i = 0; i < n; i++)
printf("w_%d %.3Lf %.3Lf\n", i, w[i].a, w[i].b);
}
然后我们就可以实现
因为还没讲
递归
int n, m;
Complex f[SIZE];
void dft(Complex *f, int len) { //当前子问题长度
if(len == 0) return;
Complex fl[len + 10], fr[len + 10];
for(int i = 0 ; i < len; i++)
fl[i] = f[i << 1], fr[i] = f[i << 1 | 1];
dft(fl, len >> 1);
dft(fr, len >> 1);
len *= 2;
Complex temp, buf;
temp.a = cos(2*pi/n), temp.b = sin(2*pi/n);
buf.a = 1;
for(int i = 0 ; i < len / 2; i++) {
f[i] = fl[i] + buf * fr[i];
f[i + len / 2] = fl[i] - buf * fr[i];
buf *= temp;
}
}
int main() {
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%Lf", &f[i].a);
for(m = 1; m < n; m <<= 1);//补到2的幂次方
dft(f, m >> 1);
for(int i = 0; i < m; i++) printf("%.3Lf ", f[i].a);
return 0;
}
事实上虚数可以用(小心TLE):
complex <double> f;
然后我们得到了一堆点值表达……
这并没有什么用
于是,我们现在来学习
刚刚的多项式为
然后就有
结论记住就好了,可以自己尝试证明。
我们可以先求出和式,然后再除以
接下来我们求出
并且仍有:
所以我们还是求出
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const long double pi = 3.141592653589793;
const int SIZE = 4e6 + 10;
struct complex { //复数
long double real, imag;
complex() { real = imag = (double)0;}
//重载复数四则运算
complex operator + (const complex x) const {
complex temp;
temp.real = real + x.real;
temp.imag = imag + x.imag;
return temp;
}
complex operator - (const complex x) const {
complex temp;
temp.real = real - x.real;
temp.imag = imag - x.imag;
return temp;
}
complex operator * (const complex x) const {
complex temp;
temp.real = real * x.real - imag * x.imag;
temp.imag = real * x.imag + imag * x.real;
return temp;
}
complex operator *= (const complex &x) {
*this = *this * x;
return *this;
}
complex operator / (const complex x) const {
double div = x.real * x.real + x.imag * x.imag;
complex temp;
temp.real = (real * x.real + imag * x.imag) / div;
temp.imag = (imag * x.real - real * x.imag) / div;
return temp;
}
void conj() { imag = -imag; }
}a[SIZE], b[SIZE];
complex omega(int n, int k) {
complex temp;
temp.real = cos(2 * pi * k / n);
temp.imag = sin(2 * pi * k / n);
return temp;
}
int n, m, p;
void fft(complex *f, int len, int inv) {
if(len == 1) return;
int mid = len >>1;
complex fl[mid + 10], fr[mid + 10];
for(int i = 0; i < mid; i++)
fl[i] = f[i << 1], fr[i] = f[i << 1 | 1];
fft(fl, mid, inv);
fft(fr, mid, inv);
complex temp, buf;
temp.real = cos(2 * pi / len);
temp.imag = sin(2 * pi / len);
buf.real = 1, buf.imag = 0;
if(inv) temp.conj();
for(int i = 0; i < mid; i++, buf *= temp) {
complex s = buf * fr[i];
f[i] = fl[i] + s;
f[i + mid] = fl[i] - s;
}
}
int main() {
scanf("%d %d", &n, &m);
for(p = 1; p < n + m + 1; p <<= 1);
for(int i = 0; i <= n; i++)
scanf("%Lf", &a[i].real);
for(int i = 0; i <= m; i++)
scanf("%Lf", &b[i].real);
fft(a, p, 0);
fft(b, p, 0);
for(int i = 0; i < p; i++)
a[i] *= b[i];
fft(a, p, 1);
for(int i = 0; i <= n + m; i++)
printf("%d ", (int)(a[i].real / p + 0.5));
return 0;
}
接着TLE了……(丝毫没有卡常的欲望)
因为递归常数巨大,并且很容易爆空间。(但实际上是因为写了
下面是AC的递归代码,以后不要写long double 了
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi = 3.141592653589793;
const int SIZE = 4e6 + 10;
struct complex { //复数
double real, imag;
complex() { real = imag = (double)0;}
//重载复数四则运算
complex operator + (const complex x) const {
complex temp;
temp.real = real + x.real;
temp.imag = imag + x.imag;
return temp;
}
complex operator - (const complex x) const {
complex temp;
temp.real = real - x.real;
temp.imag = imag - x.imag;
return temp;
}
complex operator * (const complex x) const {
complex temp;
temp.real = real * x.real - imag * x.imag;
temp.imag = real * x.imag + imag * x.real;
return temp;
}
complex operator *= (const complex &x) {
*this = *this * x;
return *this;
}
complex operator / (const complex x) const {
double div = x.real * x.real + x.imag * x.imag;
complex temp;
temp.real = (real * x.real + imag * x.imag) / div;
temp.imag = (imag * x.real - real * x.imag) / div;
return temp;
}
void conj() { imag = -imag;}
}a[SIZE], b[SIZE];
complex omega(int n, int k) {
complex temp;
temp.real = cos(2 * pi * k / n);
temp.imag = sin(2 * pi * k / n);
return temp;
}
int n, m, p;
void fft(complex *f, int len, int inv) {
if(len == 1) return;
int mid = len >>1;
complex fl[mid + 10], fr[mid + 10];
for(int i = 0; i < mid; i++)
fl[i] = f[i << 1], fr[i] = f[i << 1 | 1];
fft(fl, mid, inv);
fft(fr, mid, inv);
complex temp, buf;
temp.real = cos(2 * pi / len);
temp.imag = sin(2 * pi / len);
buf.real = 1, buf.imag = 0;
if(inv) temp.conj();
for(int i = 0; i < mid; i++, buf *= temp) {
complex s = buf * fr[i];
f[i] = fl[i] + s;
f[i + mid] = fl[i] - s;
}
}
int main() {
scanf("%d %d", &n, &m);
for(p = 1; p < n + m + 1; p <<= 1);
for(int i = 0; i <= n; i++)
scanf("%lf", &a[i].real);
for(int i = 0; i <= m; i++)
scanf("%lf", &b[i].real);
fft(a, p, 0);
fft(b, p, 0);
for(int i = 0; i < p; i++)
a[i] *= b[i];
fft(a, p, 1);
for(int i = 0; i <= n + m; i++)
printf("%d ", (int)(a[i].real / p + 0.5));
return 0;
}
实际上跑的很快的递归评测记录。
那有没有更优化的办法呢?这是肯定的。
-
蝴蝶变换
先看一张图
这张图是什么?这不是我们分治的顺序吗?
我在一开始和结束都打上了二进制,你有没有什么发现?
是的,每一个数的二进制都反过来了。
然后我们的任务就转换成求二进制反序:
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << l)//l是二进制位数
//rev[i]是i翻转得到的
结论牢记即可。
还记得貌似没什么关系)
我们用循环实现
- 第一层循环枚举变换区间大小(阶段),第二层循环枚举开头(决策),第三层处理区间。
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
const double pi = 3.141592653589793;
const int SIZE = 4e6 + 10;
struct complex { //复数
double real, imag;
complex() { real = imag = (double)0;}
//重载复数四则运算
complex operator + (const complex x) const {
complex temp;
temp.real = real + x.real;
temp.imag = imag + x.imag;
return temp;
}
complex operator += (const complex &x) {
*this = *this + x;
return *this;
}
complex operator - (const complex x) const {
complex temp;
temp.real = real - x.real;
temp.imag = imag - x.imag;
return temp;
}
complex operator * (const complex x) const {
complex temp;
temp.real = real * x.real - imag * x.imag;
temp.imag = real * x.imag + imag * x.real;
return temp;
}
complex operator *= (const complex &x) {
*this = *this * x;
return *this;
}
complex operator / (const complex x) const {
double div = x.real * x.real + x.imag * x.imag;
complex temp;
temp.real = (real * x.real + imag * x.imag) / div;
temp.imag = (imag * x.real - real * x.imag) / div;
return temp;
}
}a[SIZE], b[SIZE];
int n, m, p, L = -1;
int rev[SIZE];
void fft(complex *f, int inv) {
for(int i = 0 ; i < p ; i++)
if(i < rev[i])
swap(f[i], f[rev[i]]);
for(int len = 2; len <= p; len <<= 1) {
int length = len >> 1;
complex temp;
temp.real = cos(pi / length);
temp.imag = sin(pi / length);
if(inv) temp.imag = -temp.imag;
for(int l = 0; l < p; l += len) {
complex buf; buf.real = 1;
for(int k = l; k < l + length; k++) {
complex t = buf * f[k + length];
f[k + length] = f[k] - t;
f[k] += t;
buf *= temp;
}
}
}
}
int main() {
scanf("%d %d", &n, &m);
for(int i = 0; i <= n; i++)
scanf("%lf", &a[i].real);
for(int i = 0; i <= m; i++)
scanf("%lf", &b[i].real);
for(p = 1; p < n + m + 1; p <<= 1, L++);
//l是位数,因为会多算一位,一开始初值直接给-1
for(int i = 1; i < p; i++)
rev[i] = (rev[i >> 1] >> 1) | ((i & 1) << L);
fft(a, 0), fft(b, 0);
for(int i = 0; i < p; i++)
a[i] *= b[i];
fft(a, 1);
for(int i = 0; i <= n + m; i++)
printf("%d ", (int)(a[i].real / p + 0.5));
return 0;
}
评测记录