问题:

  已知$A=a_{0..n-1}$, $B=b_{0..n-1}$, 求$C=c_{0..2n-2}$,使:

$$c_i = \sum_{j=0}^ia_jb_{i-j}$$

  定义$C$是$A$,$B$的卷积,记作

$$C = A * B$$

  例如多项式乘法等。

  朴素做法是按照定义枚举$i$和$j$,但这样时间复杂度是$O(n^2)$.

  能不能使时间复杂度降下来呢?

点值表示法:

  我们把$A$,$B$,$C$看作多项式。

  即:

$$A(x) = \sum_{i=0}^{n-1}a_ix^i$$

  将$A=\left\{(x_1,A(x_1)), (x_2,A(x_2)), (x_3,A(x_3))...\right\}$叫做$A$的点值表示法。

  由于$A$是$n-1$次多项式,我们恰好需要$n$个点值来确定它。

  那么使用点值表示法做多项式乘法就很简单了:对应项相乘(这样的话要将$n$扩大一倍,因为$C$的次数约为$2n$)。

  那么,如何将$A$和$B$转换成点值表示法,再将$C$转化回系数表示法(即最初的表示方法)呢?

  如果任取$n$个点,按照定义计算,那么还是$O(n^2)$的。

  这样就要用到快速傅里叶变换。

快速傅里叶变换:

  既然任取$n$个点,按照定义计算太慢,就要找一些特殊点。

  我们用$n$个$n$次单位复数根($1$的$n$次方根,涉及到复数,$1$的方根不止$1$和$-1$)来计算:

  根据欧拉公式,$e^{i\theta}=\cos\theta+i\sin\theta$(其中$i$是虚数单位),那么$e^{2\pi i}=\cos(2\pi)+i\sin(2\pi)=1$.

  所以1的n次方根是$\omega_n^k=e^{\frac{2k\pi i}n}\qquad0\leq k<n$。

  其中$\omega_n^1 = e^{\frac{2\pi i}n}$是主$n$次单位根,那么所有$n$次单位复数根都是它的幂。

  我们要求出$A(\omega_n^k)$,就要采用分治思想。

  我们将奇偶系数分离(先假设n为偶数),即定义

$$A_0(x)=a_0 + a_2* x + a_4 * x^2 +\cdots=\sum_{i=0}^{\frac{n}2-1}a_{2i}x^i$$

$$A_1(x)=a_1 + a_3* x + a_5 * x^2 +\cdots=\sum_{i=0}^{\frac{n}2-1}a_{2i+1}x^i$$

  那么$A(x)=A_0(x^2) + xA_1(x^2)$。

  要计算$A(\omega_n^k)=A_0\left[(\omega_n^k)^2\right] + \omega_n^kA_1\left[(\omega_n^k)^2\right]$,

  就要用到$(\omega_n^k)^2 = \omega_{n/2}^{k\,mod (n/2)}$(证略)。

  所以$A(\omega_n^k)=A_0\left(\omega_{n/2}^{k\,mod (n/2)}\right) + \omega_n^kA_1\left(\omega_{n/2}^{k\,mod (n/2)}\right)$

  我们发现$A_0$,$A_1$都是$n/2$项的,且只需要算$\omega_{n/2}^k$的值,那么这就和开始的问题一样了,可以分治。

  边界也很容易:$n=1$的时候$A_0$本身就是值。

  合并解。

$$A(\omega_n^k)=A_0\left(\omega_{n/2}^{k\,mod (n/2)}\right) + \omega_n^kA_1\left(\omega_{n/2}^{k\,mod (n/2)}\right)$$

  那么可以$A(\omega_n^k), A(\omega_n^{k+n/2})$一起算$(0\leq k<n/2)$ :

    令$u = A_0(\omega_{n/2}^k), t = \omega_n^kA_1(\omega_{n/2}^k)$,

    那么

$$A(\omega_n^k)=u + t$$

$$\begin{aligned}& \quad A(\omega_n^{k+n/2}) \\
&= A_0(\omega_{n/2}^k) + \omega_n^{k+n/2}A_1(\omega_{n/2}^k)\\
&= A_0(\omega_{n/2}^k) + \omega_n^k\omega_n^{n/2}A_1(\omega_{n/2}^k)\\
&= A_0(\omega_{n/2}^k) - \omega_n^kA_1(\omega_{n/2}^k)\\
&= u-t\end{aligned}$$

  所以这样就能算出$A$的点值表示法。

  一个问题:分治要求$n$是$2$的幂,不是怎么办? 补$0$, 直到$n$是$2$的幂。

  时间复杂度:

  $$T(n)=2T(n/2)+O(n)$$

  直接观察或者应用主定理都可得出$T(n)=O(nlogn)$

  剩下的问题:如何把C转化回系数表示法。

逆变换:

  我们把C做一遍快速傅立叶变换,只是求的是$\omega_n^n, \omega_n^{n-1}, \cdot,\omega_n^1$的值而不是$\omega_n^0, \omega_n^1, \cdot,\omega_n^{n-1}$的值,最后每一项除以n即可。

  证明(实际上可以利用逆矩阵,但我就写的麻烦一点吧qwq):

这样我实际上是求了$c_i=\frac1n\sum_{k=0}^{n-1}(\omega_n^{-i})^kC(\omega_n^k)$。我们来直接证明这是正确的。

$$\begin{aligned}
\frac1n\sum_{k=0}^{n-1}(\omega_n^{-i})^kC(\omega_n^k)&=\frac1n\sum_{k=0}^{n-1}\omega_n^{-ki}\sum_{j=0}^{n-1}c_j(\omega_n^k)^j\\
&=\frac1n\sum_{j=0}^{n-1}c_j\sum_{k=0}^{n-1}\omega_n^{jk}\omega_n^{-ki}\\
&=\frac1n\sum_{j=0}^{n-1}c_j\sum_{k=0}^{n-1}(\omega_n^{j-i})^k
\end{aligned}$$

$\sum_{k=0}^{n-1}(\omega_n^{j-i})^k$是一个公比为$\omega_n^{j-i}$的等差数列。在$i=j$时$n$项都为$1$,显然其值为$n$;$j\neq i$的时候根据等差数列求和公式他就等于

$$\frac{1-(\omega_n^{j-i})^n}{1-\omega_n^{j-i}}$$

而$(\omega_n^{j-i})^n$等于$1$,所以上式就是$0$。于是
$$\frac1n\sum_{k=0}^{n-1}(\omega_n^{-i})^kC(\omega_n^k)=\frac1n\sum_{j=0}^{n-1}c_j\sum_{k=0}^{n-1}(\omega_n^{j-i})^k=\frac1n\sum_{j=0}^{n-1}c_j\times n[i == j]=c_i$$
证毕。

 #include <algorithm>
#include <cmath>
const double pi = acos(-1.0);
struct complex{
double real, impl;
complex(double r = 0.0, double i = 0.0) : real(r), impl(i) {}
friend complex operator+(const complex &a, const complex &b) {
return complex(a.real + b.real, a.impl + b.impl);
}
friend complex operator-(const complex &a, const complex &b) {
return complex(a.real - b.real, a.impl - b.impl);
}
friend complex operator*(const complex &a, const complex &b) {
return complex(a.real * b.real - a.impl * b.impl, a.impl * b.real + b.impl * a.real);
}
friend complex operator/(const complex &a, double b) {
return complex(a.real / b, a.impl / b);
}
};
using std::swap;
void FFT(complex* P, int len, int opt) {
for (int i = , j = , k; i < len; ++i) {
for (k = len >> ; j & k; k >>= ) j ^= k;
j ^= k;
if (i < j) swap(P[i], P[j]);
}
for (int h = ; h <= len; h <<= ) {
complex wn = complex(cos(opt * * pi / h), sin(opt * * pi / h));
for (int j = ; j < len; j += h) {
complex w = complex(1.0, .);
for (int t = ; t < h / ; ++t, w = w * wn) {
complex tmp1 = P[t + j], tmp2 = P[t + j + h / ];
P[t + j] = tmp1 + tmp2 * w;
P[t + j + h / ] = tmp1 - tmp2 * w;
}
}
}
if (opt == -)
for (int i = ; i < len; ++i)
P[i] = P[i] / len;
}

FFT

Fast Fourier Transform ——快速傅里叶变换的更多相关文章

  1. 【OI向】快速傅里叶变换(Fast Fourier Transform)

    [OI向]快速傅里叶变换(Fast Fourier Transform) FFT的作用 ​ 在学习一项算法之前,我们总该关心这个算法究竟是为了干什么. ​ (以下应用只针对OI) ​ 一句话:求多项式 ...

  2. 数字图像处理实验(5):PROJECT 04-01 [Multiple Uses],Two-Dimensional Fast Fourier Transform 标签: 图像处理MATLAB数字图像处理

    实验要求: Objective: To further understand the well-known algorithm Fast Fourier Transform (FFT) and ver ...

  3. 「学习笔记」Fast Fourier Transform

    前言 快速傅里叶变换(\(\text{Fast Fourier Transform,FFT}\) )是一种能在\(O(n \log n)\)的时间内完成多项式乘法的算法,在\(OI\)中的应用很多,是 ...

  4. $\mathcal{FFT}$·$\mathcal{Fast \ \ Fourier \ \ Transformation}$快速傅立叶变换

    \(2019.2.18upd:\) \(LINK\) 之前写的比较适合未接触FFT的人阅读--但是有几个地方出了错,大家可以找一下233 啊-本来觉得这是个比较良心的算法没想到这么抽搐这个算法真是将一 ...

  5. Fast Walsh-Hadamard Transform——快速沃尔什变换

    模板题: 给定$n = 2^k$和两个序列$A_{0..n-1}$, $B_{0..n-1}$,求 $$C_i = \sum_{j \oplus k = i} A_j B_k$$ 其中$\oplus$ ...

  6. Fast Walsh-Hadamard Transform——快速沃尔什变换(二)

    上次的博客有点模糊的说...我把思路和算法实现说一说吧... 思路 关于快速沃尔什变换,为了方便起见,我们采用线性变换(非线性变换不会搞). 那么,就会有一个变化前各数值在变换后各处的系数,即前一篇博 ...

  7. 快速傅里叶变换(Fast Fourier Transform, FFT)和短时傅里叶变换(short-time Fourier transform,STFT )【资料整理】【自用】

    1. 官方形象展示FFT:https://www.bilibili.com/video/av19141078/?spm_id_from=333.788.b_636f6d6d656e74.6 2. 讲解 ...

  8. 1250 Super Fast Fourier Transform(湘潭邀请赛 暴力 思维)

    湘潭邀请赛的一题,名字叫"超级FFT"最终暴力就行,还是思维不够灵活,要吸取教训. 由于每组数据总量只有1e5这个级别,和不超过1e6,故先预处理再暴力即可. #include&l ...

  9. Fast Fourier Transform

    写在前面的.. 感觉自己是应该学点新东西了.. 所以就挖个大坑,去学FFT了.. FFT是个啥? 挖个大坑,以后再补.. 推荐去看黑书<算法导论>,讲的很详细 例题选讲 1.UOJ #34 ...

随机推荐

  1. freemarker导出word文档——WordXML格式解析

    前不久,公司一个项目需要实现导出文档的功能,之前是一个同事在做,做了3个星期,终于完成了,但是在项目上线之后却发现导出的文档有问题,此时,这个同事已经离职,我自然成为接班者,要把导出功能实现,但是我看 ...

  2. 浅谈java类集框架和数据结构(2)

    继续上一篇浅谈java类集框架和数据结构(1)的内容 上一篇博文简介了java类集框架几大常见集合框架,这一篇博文主要分析一些接口特性以及性能优化. 一:List接口 List是最常见的数据结构了,主 ...

  3. Tcl与Design Compiler (十一)——其他的时序约束选项(二)

    本文如果有错,欢迎留言更正:此外,转载请标明出处 http://www.cnblogs.com/IClearner/  ,作者:IC_learner 前面介绍的设计都不算很复杂,都是使用时钟的默认行为 ...

  4. 简单易用的.NET免费开源RabbitMQ操作组件EasyNetQ解析

    对于目前大多的.NET项目,其实使用的技术栈都是差不多,估计现在很少用控件开发项目的了,毕竟一大堆问题.对.NET的项目,目前比较适合的架构ASP.NET MVC,ASP.NET WebAPI,ORM ...

  5. jQuery基础学习(二)—jQuery选择器

    一.jQuery基本选择器 1.CSS选择器     在学习jQuery选择器之前,先介绍一下之前学过的CSS选择器. 选择器 语法 描述 示例   标签选择器 E {                 ...

  6. C#中的泛型和泛型集合

    泛型 泛型引入了一个概念:类型参数.通过使用类型参数(T)减少了运行时强制转换或装箱操作的风险,通过泛型可以最大限度的重用代码,保护类型的安全及提高性能,他的最常见应用就是创建集合类,可以约束集合类中 ...

  7. kindeditor修改图片上传路径-使用webapi上传图片到图片服务器

    kindeditor是一个非常好用的富文本编辑器,它的简单使用我就不再介绍了. 在这里我着重介绍一些使用kindeditor修改图片上传路径并通过webapi上传图片到图片服务器的方案. 因为我使用的 ...

  8. java代码开发完成后,代码走查规范

    代码走查注意事项: 1.不变的值,尽量写个常量类 2.尽量使用if{}else,不要一直if去判断 3.减少循环调用方法查询数据库 4.dao层尽量不要用逻辑,尽量在service里写业务逻辑 5.金 ...

  9. Angular2.js——主从结构

    学习这一篇的内容,还回到我们快速起步的应用上来. 主从结构,我们想要做一个英雄的列表,我们希望用户在列表中选中一个英雄,然后这个被选中的英雄出现在详情视图中.主视图是英雄列表,从视图则是被选中英雄的详 ...

  10. webService基础知识--认识WebService

    之前在找工作的时候,有面试官问到WebService,当时没有接触过,正好现在做的项目中有用到WebService,所以就趁着业余时间来学习了. 一.简介 先来看看百度百科对WebService的解释 ...