$FFT$(快速傅里叶变换)
- 概念引入
- 点值表示
对于一个$n - 1$次多项式$A(x)$,可以通过确定$n$个点与值(即$x$和$y$)来表示这唯一的$A(x)$
- 复数
对于一元二次方程
$$x^2 + 1 = 0$$
在实数范围内无解,那么我们将实数范围扩充,就得到了复数,再令$i$为该方程的解,即
$$i^2 = - 1$$
那么就定义$z = a + bi$的数为复数,则有
当$b = 0$时,$z$为实数
当$b \neq 0$时,$z$为虚数
当$a = 0 \ \&\& \ b \neq 0$时,$z$为纯虚数
其中,复数满足加法交换律、结合律、乘法分配率等
复数相乘:$z_1 = a_1 + b_1i, \ z_2 = a_2 + b_2i$,则$z_1 × z_2 = (a_1 + b_1i)(a_2 + b_2i) = (a_1a_2 - b_1b_2) + (a_1b_2 + b_1a_2)i$,它们相乘还是一个复数,在复平面上理解,就是模长相乘,幅角相加
共轭复数:当两个复数实部相同,虚部为相反数时,两个复数被称为共轭复数
对于一个复数$z = a + bi$,还可以在复数平面上用向量表示出来,即有$\overrightarrow{OZ}$一一对应每个$z = a + bi$,那么就有复数的模等于$\overline{Z}$,即$|z| = \sqrt{a^2 + b^2}$
复数直接比较大小没有意义,除非它是一个实数
- $FFT$作用
那么,有了点值表示,对于多项式$A(x)$和$B(x)$相乘,就不需要$O(n^2)$,而只需要$O(n)$,因为$C(x_i) = A(x_i) * B(x_i)$,然后枚举$x_i$即可
于是现在的复杂度症结就在于将多项式转化成点值表示的$O(n^2)$了,于是就有$FFT$,可以实现在$O(n \ logn)$的时间内转化
- 离散傅里叶变换
于是傅里叶规定,点值中的$x$为模长为$1$的复数
至于为什么要取复数而不是实数,因为它有很神奇的性质
那么对于这$n$个复数的取法,取的是复平面上半径为$1$的一个圆,将其$n$等分后得到的点,令第$i$个点表示为$\omega_n^k(0 \le k \le n - 1)$,那么这个点在复平面中的表示即为$(cos\frac{k}{n}2\pi, \ sin\frac{k}{n}2\pi)$,那么根据复数乘法在复平面上的意义为模长相乘,幅长相加,即$\omega_n^k$相当于$(\omega_n^1)^k$,那么称$\omega_n^1$为单位根
我们就把这$\omega_n^0, \ \omega_n^1, \ ..., \ \omega_n^{n - 1}$作为点值表示的$x$,称作离散傅里叶变换的结果
先给出关于傅里叶逆变换的结论:
- 将多项式$A(x)$的离散傅里叶变换结果作为系数代入多项式$B(x)$,再将每个离散傅里叶变换结果的倒数,即$\omega_n^0, \ \omega_n^{- 1}, \ ..., \ \omega_n^{- (n - 1)}$作为$x$代入$B(x)$得到点值表示,那么这些表示除以$n$就得到了$A(x)$的原系数 [至于证明:不存在的]
这就是傅里叶变换神奇的性质
- $FFT$
有了傅里叶变换,虽然多项式与点值表示相互转化已经很轻松了,但是复杂度仍然不理想,就有了快速傅里叶变换
结论:
- $\omega_{2n}^{2k} = \omega_n^k$ [证明:代进原公式显然]
- $\omega_n^{k + \frac{n}{2}} = - \omega_n^k$ [证明:关于复平面原点中心对称]
对于多项式$A(x) = \sum\limits_{i = 0}^{n - 1} a_ix^i$,将它的奇偶项拆开,并将$x$转为$x^2$得到(此处先假定$n$为偶数)
当然此时代入的是$N(x^2), \ M(x^2)$,则有
此时就需要把$\omega_n^k$代入,分情况讨论:
若$k < \frac{n}{2}$,则有
反之
于是,我们只要求得$\{\omega_{\frac{n}{2}}^0, \ \omega_{\frac{n}{2}}^1, \ ..., \ \omega_{\frac{n}{2}}^{\frac{n}{2} - 1}\}$,就可以得到$A(x)$的所有关于所有离散傅里叶变换结果的点值表示了,可用分治实现,复杂度$O(n \ logn)$
- 代码(分治FFT)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> using namespace std; const int MAXN = 2e06 + ; const double PI = acos (- 1.0); int N, M; struct mcomplex {
double x, y; mcomplex () {}
mcomplex (double fx, double fy) :
x (fx), y (fy) {} mcomplex operator + (const mcomplex& p) const {
return mcomplex (x + p.x, y + p.y);
}
mcomplex operator - (const mcomplex& p) const {
return mcomplex (x - p.x, y - p.y);
}
mcomplex operator * (const mcomplex& p) const {
return mcomplex (x * p.x - y * p.y, x * p.y + y * p.x);
}
} ;
mcomplex omega (int n, int k, int inv) {
return mcomplex (cos (2.0 * PI * (double) k / (double) n), 1.0 * inv * sin (2.0 * PI * (double) k / (double) n)); // 当一个复数的模长为1时,它的共轭复数即为它的倒数
}
mcomplex A[MAXN], B[MAXN]; void FFT (mcomplex* a, int n, int inv) { // inv表示是否取倒数
if (n == )
return ;
mcomplex a1[n >> ], a2[n >> ];
int m = n >> ;
for (int i = ; i <= n; i += ) {
a1[i >> ] = a[i];
a2[i >> ] = a[i + ];
}
FFT (a1, m, inv);
FFT (a2, m, inv);
for (int i = ; i < m; i ++) {
mcomplex x = omega (n, i, inv);
a[i] = a1[i] + x * a2[i];
a[i + m] = a1[i] - x * a2[i];
}
} int getnum () {
int num = ;
char ch = getchar (); while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << ) + (num << ) + ch - '', ch = getchar (); return num;
} int main () {
N = getnum (), M = getnum ();
for (int i = ; i <= N; i ++)
A[i].x = (double) getnum ();
for (int i = ; i <= M; i ++)
B[i].x = (double) getnum (); int n;
for (n = ; n <= N + M; n <<= );
FFT (A, n, );
FFT (B, n, );
for (int i = ; i <= n; i ++)
A[i] = A[i] * B[i];
FFT (A, n, - );
for (int i = ; i <= N + M; i ++) {
if (i)
putchar (' ');
printf ("%d", (int) (A[i].x / n + 0.5));
}
puts (""); return ;
} /*
1 2
1 2
1 2 1
*/ /*
5 5
1 7 4 0 9 4
8 8 2 4 5 5
*/
分治FFT
- 蝴蝶操作
于是这样还是会超时,那么还需要优化
根据表格,有一个考眼力的性质
$|0 \ 1 \ 2 \ 3|$
$|0 \ 2 | 1 \ 3|$
$|0 | 2 | 1 | 3|$
会发现每个数字的目标位置的二进制是原数的二进制翻转的结果,比如$1 = (01)_2, \ 2 = (10)_2$恰好是相对应的,于是就可以根据这个性质先将每个数排列到最终位置,再逐一合并
- 代码(迭代优化$FFT$)
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath> using namespace std; const int MAXN = ( << ); const double PI = acos (- 1.0); int N, M; struct mcomplex {
double x, y; mcomplex () {}
mcomplex (double fx, double fy) :
x (fx), y (fy) {} mcomplex operator + (const mcomplex& p) const {
return mcomplex (x + p.x, y + p.y);
}
mcomplex operator - (const mcomplex& p) const {
return mcomplex (x - p.x, y - p.y);
}
mcomplex operator * (const mcomplex& p) const {
return mcomplex (x * p.x - y * p.y, x * p.y + y * p.x);
}
} ;
mcomplex omega (int n, int k, int inv) {
return mcomplex (cos (2.0 * PI * (double) k / (double) n), 1.0 * inv * sin (2.0 * PI * (double) k / (double) n));
}
mcomplex A[MAXN], B[MAXN]; int oppo[MAXN];
int limit;
void FFT (mcomplex* a, int inv) {
for (int i = ; i < limit; i ++)
if (i < oppo[i])
swap (a[i], a[oppo[i]]);
for (int mid = ; mid < limit; mid <<= ) { // 枚举区间长度的一半
mcomplex ome = mcomplex (cos (PI / (double) mid), inv * sin (PI / (double) mid)); // 单位根
for (int n = mid << , j = ; j < limit; j += n) {
mcomplex x = mcomplex (, );
for (int k = ; k < mid; k ++, x = x * ome) {
mcomplex a1 = a[j + k], xa2 = x * a[j + k + mid]; // 蝴蝶操作
a[j + k] = a1 + xa2;
a[j + k + mid] = a1 - xa2;
}
}
}
} int getnum () {
int num = ;
char ch = getchar (); while (! isdigit (ch))
ch = getchar ();
while (isdigit (ch))
num = (num << ) + (num << ) + ch - '', ch = getchar (); return num;
} int main () {
N = getnum (), M = getnum ();
for (int i = ; i <= N; i ++)
A[i].x = (double) getnum ();
for (int i = ; i <= M; i ++)
B[i].x = (double) getnum (); int n, lim = ;
for (n = ; n <= N + M; n <<= , lim ++);
for (int i = ; i <= n; i ++)
oppo[i] = (oppo[i >> ] >> ) | ((i & ) << (lim - ));
// 原来是左移,反转后改成右移,并且处理一下奇数原末尾的1
limit = n;
FFT (A, );
FFT (B, );
for (int i = ; i <= n; i ++)
A[i] = A[i] * B[i];
FFT (A, - );
for (int i = ; i <= N + M; i ++) {
if (i)
putchar (' ');
printf ("%d", (int) (A[i].x / n + 0.5));
}
puts (""); return ;
} /*
1 2
1 2
1 2 1
*/ /*
5 5
1 7 4 0 9 4
8 8 2 4 5 5
*/
迭代优化FFT
- 总结
这样的$FFT$可以在$O(n \ logn)$的时间内求出多项式乘法的各项系数,主要流程:将两个多项式分别转化成点值表示 $\dashrightarrow$ 通过点值表示将两个多项式合并 $\dashrightarrow$ 通过离散傅里叶逆变换将点值表示转化成系数表示,即得解
- 参考资料
$FFT$(快速傅里叶变换)的更多相关文章
- FFT 快速傅里叶变换 学习笔记
FFT 快速傅里叶变换 前言 lmc,ikka,attack等众多大佬都没教会的我终于要自己填坑了. 又是机房里最后一个学fft的人 早背过圆周率50位填坑了 用处 多项式乘法 卷积 \(g(x)=a ...
- CQOI2018 九连环 打表找规律 fft快速傅里叶变换
题面: CQOI2018九连环 分析: 个人认为这道题没有什么价值,纯粹是为了考算法而考算法. 对于小数据我们可以直接爆搜打表,打表出来我们可以观察规律. f[1~10]: 1 2 5 10 21 4 ...
- 「学习笔记」FFT 快速傅里叶变换
目录 「学习笔记」FFT 快速傅里叶变换 啥是 FFT 呀?它可以干什么? 必备芝士 点值表示 复数 傅立叶正变换 傅里叶逆变换 FFT 的代码实现 还会有的 NTT 和三模数 NTT... 「学习笔 ...
- FFT —— 快速傅里叶变换
问题: 已知A[], B[], 求C[],使: 定义C是A,B的卷积,例如多项式乘法等. 朴素做法是按照定义枚举i和j,但这样时间复杂度是O(n2). 能不能使时间复杂度降下来呢? 点值表示法: 我们 ...
- [C++] 频谱图中 FFT快速傅里叶变换C++实现
在项目中,需要画波形频谱图,因此进行查找,不是很懂相关知识,下列代码主要是针对这篇文章. http://blog.csdn.net/xcgspring/article/details/4749075 ...
- matlab中fft快速傅里叶变换
视频来源:https://www.bilibili.com/video/av51932171?t=628. 博文来源:https://ww2.mathworks.cn/help/matlab/ref/ ...
- FFT快速傅里叶变换算法
1.FFT算法概要: FFT(Fast Fourier Transformation)是离散傅氏变换(DFT)的快速算法.即为快速傅氏变换.它是根据离散傅氏变换的奇.偶.虚.实等特性,对离散傅立叶变换 ...
- FFT快速傅里叶变换
FFT太玄幻了,不过我要先膜拜HQM,实在太强了 1.多项式 1)多项式的定义 在数学中,由若干个单项式相加组成的代数式叫做多项式.多项式中的每个单项式叫做多项式的项,这些单项式中的最高项次数,就是这 ...
- [学习笔记]FFT——快速傅里叶变换
大力推荐博客: 傅里叶变换(FFT)学习笔记 一.多项式乘法: 我们要明白的是: FFT利用分治,处理多项式乘法,达到O(nlogn)的复杂度.(虽然常数大) FFT=DFT+IDFT DFT: 本质 ...
- FFT(快速傅里叶变换)
学习了FFT用来求多项式的乘法,看了算导上的介绍,上面讲的非常明白,概括一下FFT的原理就是,我们在计算多项式的乘法时,如果暴力模拟的话是n^2 复杂度的,就像小学学的竖式乘法一样,比如一个n位数乘上 ...
随机推荐
- SDOI2017 解题报告
数字表格 \(T\)次询问,每次给出\(n,m(n,m\le 10^6)\),\(f\)为斐波那契数列,\(f_0=0,f_1=1\),求: \[ \prod _{i=1}^n\prod _{j=1} ...
- 洛谷P3656 展翅翱翔之时 (はばたきのとき)(洛谷2017.3月赛round1 t4)
题目背景 船が往くよミライへ旅立とう 船只启航 朝未来展开旅途 青い空笑ってる(なにがしたい?) 湛蓝天空露出微笑(想做些什么?) ヒカリになろうミライを照らしたい 化作光芒吧 想就此照亮未来 輝きは ...
- 秒杀多线程第十四篇 读者写者问题继 读写锁SRWLock (续)
java 包实现了读写锁的操作: package com.multithread.readwritelock; import java.util.concurrent.CountDownLatch; ...
- 51nod 1532 带可选字符的多字符串匹配(位运算)
题意: 有一个文本串,它的长度为m (1 <= m <= 2000000),现在想找出其中所有的符合特定模式的子串位置.符合特定模式是指,该子串的长度为n (1 <= n <= ...
- BZOJ1089 [SCOI2003]严格n元树 【dp + 高精】
Description 如果一棵树的所有非叶节点都恰好有n个儿子,那么我们称它为严格n元树.如果该树中最底层的节点深度为d (根的深度为0),那么我们称它为一棵深度为d的严格n元树.例如,深度为2的严 ...
- HDU.1394 Minimum Inversion Number (线段树 单点更新 区间求和 逆序对)
HDU.1394 Minimum Inversion Number (线段树 单点更新 区间求和 逆序对) 题意分析 给出n个数的序列,a1,a2,a3--an,ai∈[0,n-1],求环序列中逆序对 ...
- Ciesz się Polską
SZKOpułPoi at BZOJPA at BZOJONTAK at BZOJ Chinese Solution of Poi
- 前端基础----JavaScript基础
一.JavaScript概述 1,JavaScript的历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名ScriptEa ...
- python安装包下载
加入python官网一次按照下图点击: 这个exe文件就下好了,然后再安装一下即可.
- @Springboot搭建项目controller层接收json格式的对象失败
今天在使用swagger2测试的时候出错 1.@requestBody注解常用来处理content-type不是默认的application/x-www-form-urlcoded编码的内容,比如说: ...