快速傅里叶变换FFT(Fast Fourior Transform)

先说一下它能干嘛qwq

​ 傅里叶变换有两种,连续傅里叶变换和离散傅里叶变换,OI中主要用来快速计算多项式卷积。

等一下,卷积是啥》》

​ 卷积可以通俗地理解成把两个多项式相乘,比如 : \((x^2+x)*(x+2)=x^3+2x^2+2x\)

​ 对于多项式的系数来说,就是求这个柿子:

​ 给定两个多项式 \(A(x), B(x):​\) ​

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

\[B(x)=\sum_{i=0}^{n-1} {b_ix^i}
\]

​ 相乘得到 \(C(x):​\)

\[C(x)=\sum_{j+k=i , 0 \leq j , k < n} {a_jb_kx^i}
\]

​ 直接暴力计算复杂度是 \(O(n^2),​\) 显然不够优秀\(,​\) 所以要用FFT快速计算\(;​\)

前置知识

​ 复数初步\(;\) 多项式表达 \(\ldots \ldots\)

正文部分

一、多项式的表达

​ 系数表达\(:\) 一般我们写的形如 \(x^2+5x-1\) 这样的柿子可以表示为向量 \(\vec {a}=(1,5,-1)\)

​ 点值表达\(:\) 给定\(n\)个点\(,\) 可以确定一个\(n-1\)次多项式\(;\) 一个多项式有无数种点值表达\(;\)

​ 将点值表达转换为系数表达的过程叫做插值\(,\) 常用的有拉格朗日插值法 \(;\)

​ 显然对于两个多项式的点值表达横坐标采样点相同\(,\) 就可以直接相乘,时间复杂度\(O(n)\)。

二、复数\((complex)\)初步 \(:\) 单位根

​ \(n\) 次单位根指满足 \(z^n=1\) 的复数,一共有 \(n\) 个\(,\) 即 \(\omega_n^k=e^{\frac{2 \pi ik}{n}}\) \(,\) $k \in [0 , n-1]; $ (强烈吐槽数学公式渲染qaq

​ 根据欧拉公式 \(e^{ik}=\cos(k)+i\sin(k)​\) \(,​\) 所以 \(\omega_n^k=cos(\frac{2k \pi}{n})+i\sin(\frac{2k \pi}{n});​\)

​ 几何意义\(:\) \(n\) 次单位根均匀分布在复平面的单位圆上\(;\) \(p.s.\) 复数相乘\(:\) 模长相乘\(,\) 幅角相加

​ \(n=8\) 时\(,\) 如下图\(:\)

三、DFT与IDFT

\(1.DFT\)

​ DFT$(Discrete $ \(Fourier\) \(Transform)\) 可以在 \(O(n\) \(lb\) \(n)\) 的时间内把多项式的系数表达转为点值表达\(;\)

​ 一个多项式的系数表达 \(\vec a=(a_0,a_1,a_2……a_n-1)\) 可以写成一个矩阵列向量\(a:\)

\[\left [
\begin{matrix}
a_0 \\
a_1 \\
a_2 \\
a_3 \\
\vdots \\
a_n-1
\end{matrix}
\right ]
\]

​ 把\(n​\)个单位复数根带入该多项式可以得到如下方程组\(;​\)

\[\begin{cases}
a_0(\omega_n^0)^0+a_1(\omega_n^0)^1+a_2(\omega_n^0)^2+\cdots+a_{n-2}(\omega_n^0)^{n-2}+a_{n-1}(\omega_n^0)^{n-1}=A_0 \\
a_0(\omega_n^1)^0+a_1(\omega_n^1)^1+a_2(\omega_n^1)^2+\cdots+a_{n-2}(\omega_n^1)^{n-2}+a_{n-1}(\omega_n^1)^{n-1}=A_1 \\
\vdots \\
a_0(\omega_n^{n-1})^0+a_1(\omega_n^{n-1})^1+a_2(\omega_n^{n-1})^2+\cdots+a_{n-2}(\omega_n^{n-1})^{n-2}+a_{n-1}(\omega_n^{n-1})^{n-1}=A_{n-1} \\
\end{cases}
\]

​ 其中\(,\) \(A\)即为点值表达;

​ 写成矩阵形式\(:\)

\[\left[
\begin{matrix}
(\omega_n^0)^0 & (\omega_n^0)^1 & (\omega_n^0)^2 & \cdots & (\omega_n^0)^{n-1} \\
(\omega_n^1)^0 & (\omega_n^1)^1 & (\omega_n^1)^2 & \cdots & (\omega_n^1)^{n-1} \\
(\omega_n^2)^0 & (\omega_n^2)^1 & (\omega_n^2)^2 & \cdots & (\omega_n^2)^{n-1} \\
\vdots & \vdots & \vdots& \vdots& \vdots\\
(\omega_n^{n-1})^0 & (\omega_n^{n-1})^1 & (\omega_n^{n-1})^2 & \cdots & (\omega_n^{n-1})^{n-1}
\end{matrix}
\right ]
\left[
\begin{matrix}
a_0 \\
a_1 \\
a_2 \\
\vdots \\
a_{n-1}
\end{matrix}
\right ]
=
\left[
\begin{matrix}
A_0 \\
A_1 \\
A_2 \\
\vdots \\
A_{n-1}
\end{matrix}
\right ]
\]

\(2.IDFT\)

​ 为了方便表示\(,​\) 不妨设:

\[V=
\left [
\begin{matrix}
(\omega_n^0)^0 & (\omega_n^0)^1 & (\omega_n^0)^2 & \cdots & (\omega_n^0)^{n-1} \\
(\omega_n^1)^0 & (\omega_n^1)^1 & (\omega_n^1)^2 & \cdots & (\omega_n^1)^{n-1} \\
(\omega_n^2)^0 & (\omega_n^2)^1 & (\omega_n^2)^2 & \cdots & (\omega_n^2)^{n-1} \\
\vdots & \vdots & \vdots& \ddots& \vdots\\
(\omega_n^{n-1})^0 & (\omega_n^{n-1})^1 & (\omega_n^{n-1})^2 & \cdots & (\omega_n^{n-1})^{n-1} \\
\end {matrix}
\right ]
\\
D=
\left [
\begin{matrix}
(\omega_n^0)^0 & (\omega_n^0)^1 & (\omega_n^0)^2 & \cdots & (\omega_n^0)^{n-1} \\
(\omega_n^{-1})^0 & (\omega_n^{-1})^1 & (\omega_n^{-1})^2 & \cdots & (\omega_n^{-1})^{n-1} \\
(\omega_n^{-2})^0 & (\omega_n^{-2})^1 & (\omega_n^{-2})^2 & \cdots & (\omega_n^{-2})^{n-1} \\
\vdots & \vdots & \vdots& \ddots& \vdots\\
(\omega_n^{-(n-1)})^0 & (\omega_n^{-(n-1)})^1 & (\omega_n^{-(n-1)})^2 & \cdots & (\omega_n^{-(n-1)})^{n-1} \\
\end {matrix}
\right ]
\\
\]

再回顾一下IDFT要解决的问题\(,\) 已知点值表达\(,\) 求系数表达\(,\) 即\(:\) 已知 \(A\) 求 \(a\)

根据DFT的过程\(,\) 显然可以得到\(:\) \(V \cdot a=A\)

问题就转化为 \(a=V^{-1} \cdot A\)

​ 接下来开始填鸭\(:\)

​ (1) 已知一个复数 \(z=a+bi\) \(,\) 则它的共轭复数为 \(z'=a-bi\) \(,\) 显然 $ z \cdot z'=a2+b2,$

​ 且 \(\omega_n^k\) 的共轭复数为 \(\omega_n^{-k}\) (可以考虑单位根的几何意义)\(;\)

​ 不难发现 \(D\) 中的每个数都是 \(V\) 中对应位置上的共轭复数\(;​\)

​ \(D\) 即为 \(V\) 的共轭矩阵;

​ (2) 两个共轭复数相乘是一个实数,可以尝试性地写出 \(E=V \cdot D ;\)

\[E=
\left [
\begin{matrix}
(\omega_n^0)^0 & (\omega_n^0)^1 & (\omega_n^0)^2 & \cdots & (\omega_n^0)^{n-1} \\
(\omega_n^1)^0 & (\omega_n^1)^1 & (\omega_n^1)^2 & \cdots & (\omega_n^1)^{n-1} \\
(\omega_n^2)^0 & (\omega_n^2)^1 & (\omega_n^2)^2 & \cdots & (\omega_n^2)^{n-1} \\
\vdots & \vdots & \vdots& \ddots& \vdots\\
(\omega_n^{n-1})^0 & (\omega_n^{n-1})^1 & (\omega_n^{n-1})^2 & \cdots & (\omega_n^{n-1})^{n-1} \\
\end {matrix}
\right ]
\left [
\begin{matrix}
(\omega_n^0)^0 & (\omega_n^0)^1 & (\omega_n^0)^2 & \cdots & (\omega_n^0)^{n-1} \\
(\omega_n^{-1})^0 & (\omega_n^{-1})^1 & (\omega_n^{-1})^2 & \cdots & (\omega_n^{-1})^{n-1} \\
(\omega_n^{-2})^0 & (\omega_n^{-2})^1 & (\omega_n^{-2})^2 & \cdots & (\omega_n^{-2})^{n-1} \\
\vdots & \vdots & \vdots& \ddots& \vdots\\
(\omega_n^{-(n-1)})^0 & (\omega_n^{-(n-1)})^1 & (\omega_n^{-(n-1)})^2 & \cdots & (\omega_n^{-(n-1)})^{n-1} \\
\end {matrix}
\right ]
\\
\]

​ 当 \(i=j\) 时\(,\) \(e_{i,j}=\sum_{k=0}^{n-1}v_{i,k} \cdot d_{k,j}=\sum_{k=0}^{n-1} (\omega_n^{i-j})^k=n\)

​ 当 $ i \ne j ​$ 时\(,​\) \(e_{i,j}=\sum_{k=0}^{n-1}v_{i,k} \cdot d_{k,j}=\sum_{k=0}^{n-1}(\omega_n^{(i-j)})^k,​\) 不难发现这是个等比数列\(,​\) 所以\(:​\)​

​ \(e_{i,j}=\frac{\omega_n^{(i-j)n}-1} {\omega_n ^ {i-j} - 1}=0​\)

​ 综上\(,\) \(E\)是一个$n \times n $ 的矩阵\(:\)

\[E=
\left [
\begin{matrix}
n & 0 & 0 & 0 &\cdots& 0 & 0 \\
0 & n & 0 & 0 &\cdots& 0 & 0 \\
0 & 0 & n & 0 &\cdots& 0 & 0 \\
0 & 0 & 0 & n &\cdots& 0 & 0 \\
\vdots & \vdots & \vdots & \vdots &\ddots &\vdots &\vdots \\
0 & 0 & 0 & 0 &\cdots& n & 0 \\
0 & 0 & 0 & 0 &\cdots& 0 & n \\
\end{matrix}
\right]
\]

​ 线性代数中\(,\) 矩阵主对角线上的数都是 \(n\) 的矩阵可以看成是数 \(n,\) 这个可以通过矩阵乘法验证\(,\) 不再赘述\(;\)

​ 至此\(,\) 求多项式卷积的复杂度仍然是 \(O(n^2),\) 后面的内容就是加速的方法,这里附一张图方便理解\(:\)

四、FFT的实现

​ \(p.s.\) 这里默认 \(n=2^k, k \in Z\)

​ 关于单位根的一些性质\(:\)

​ 1.消去引理 \(\omega_{dn}^{dk}=e^{\frac{2 \pi idk}{dn}}=e^{\frac{2 \pi ik}{n}}=\omega_n^k\)

​ 2.折半引理 \(\omega_n^{k+ \frac{n}{2}}=-\omega_n^k​\)

​ \(prf:\) \(\because \omega_n^n=1\)

​ \(\therefore \omega_n^{\frac{n}{2}} = \pm 1\)

​ 又 \(\because \omega_n^{\frac{n}{2}} \ne \omega_n^n\)

​ \(\therefore \omega_n^{\frac{n}{2}}=-1\)

​ \(\therefore \omega_n^{k+ \frac{n}{2}}=-\omega_n^k\)

​ 设 \(A_0(x)=a_0x^0+a_2x+a_4x^2+ \cdots +a_{n-2}x^{\frac{n}2-1},​\) \(A_1(x)=a_1x^0+a_3x^1+a_5x^2+ \cdots +a_{n-1}x^{\frac{n}2-1}​\)

​ 则 \(A(x)=A_0(x^2)+xA_1(x^2);​\)

​ 把 \(x=\omega_n^k\) 代入\(,\) 根据消去引理可得\(:\) \((\omega_n^k)^2 = \omega_{\frac{n}{2}}^k\)

​ 所以柿子可以写成\(:\)

\[A(\omega_n^k)=A_0(\omega_{\frac{n}{2}}^k)+\omega_n^k(A_1(\omega_{\frac{n}{2}}^k) \\
A(\omega_n^{k+\frac{n}{2}})=A_0(\omega_{\frac{n}{2}}^k)-\omega_n^k(A_1(\omega_{\frac{n}{2}}^k)\\
\]

​ 直接递归实现就好了\(,\) 和之前说的一样\(,\) 要把 \(n\) 补齐 \(2\) 的次幂\(,\) 代码如下\(;\)

​ \(p.s.\) 有个小的常数优化 \(——\) 复数手写\(,\) 不用 \(stl\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
typedef double db;
typedef long long ll;
inline int in() {
int x=0;char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48), c=getchar();
return x;
}
template<typename T>
inline void out(T x) {
int cnt=0;
static char s[20];
do s[cnt++]=x%10; while(x/=10);
while(cnt--) putchar(s[cnt]+48);
putchar(' ');
} const int N = (int)3e6+5;
const db PI=acos(-1);
struct cp {
db real, imag;
cp(db x=0, db y=0) { this->real=x, this->imag=y; }
inline cp operator + (const cp x) { return cp(this->real+x.real, this->imag+x.imag); }
inline cp operator - (const cp x) { return cp(this->real-x.real, this->imag-x.imag); }
inline cp operator * (const cp x) {
return cp(this->real*x.real-this->imag*x.imag, this->real*x.imag+this->imag*x.real);
}
};
cp x[N], y[N];
int n, m, nn; void fft(int n, cp *a, int k) {
if(n==1) return ;
int nn=n>>1;
cp a0[nn+1], a1[nn+1];
for(int i=0;i<nn;i++) a0[i]=a[i<<1], a1[i]=a[i<<1|1];
fft(nn, a0, k); fft(nn, a1, k);
cp omega_n(cos(2*PI/n), sin(k*2*PI/n)), omega(1, 0);
for(int i=0;i<nn;i++) {
a[i]=a0[i]+omega*a1[i];
a[i+nn]=a0[i]-omega*a1[i];
omega=omega*omega_n;
}
} int main() {
n=in(), m=in();
for(int i=0;i<=n;i++) x[i].real=in();
for(int i=0;i<=m;i++) y[i].real=in();
m+=n, nn=1;
while(nn<=m) nn<<=1;
fft(nn, x, 1); fft(nn, y, 1);
for(int i=0;i<nn;i++) x[i]=x[i]*y[i];
fft(nn, x, -1);
for(int i=0;i<=m;i++) out((int)(x[i].real/nn+0.5));
putchar('\n');
return 0;
}

五、迭代实现

​ 在递归实现时\(,\) 把当前的 \(A\) 奇偶分组\(,\) 实际上是按末尾二进制位分组\(,\) 不妨考虑 \([0, n-1]\) 的二进制\(;\)

​ 每次递归分组时\(,\) 显然二进制是从最低位到最高位产生影响\(,\) 可以得到如下结论\(:\)

​ \(A\) 中的每个数在递归终止时所在的位置的编号\(,\) 就是把它开始所在位置二进制反过来得到的\(;\)

​ 简单验证一下:

Before - - End
0 000 000 0
1 001 100 4
2 010 010 2
3 011 110 6
4 100 001 1
5 101 101 5
6 110 011 3
7 111 111 7

​ 那么问题来了\(,\) 代码怎么实现》》》

​ \(rev_i=rev_{i/2}/2+(i\) \(mod\) \(2)*(\frac{n}{2})\)

​ 把 \(i\) 右移一位\(,\) 反过来\(,\) 再把之前在开头的 \(0\) 移走\(,\) 再把最后一位的贡献计算出来即可\(;\)

​ 上代码 \(:\)

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
typedef double db;
typedef long long ll;
inline int in() {
int x=0;char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48), c=getchar();
return x;
}
template<typename T>
inline void out(T x) {
int cnt=0;
static char s[20];
do s[cnt++]=x%10; while(x/=10);
while(cnt--) putchar(s[cnt]+48);
putchar(' ');
} const int N = (int)3e6+5;
const db PI=acos(-1);
struct cp {
db real, imag;
cp(db x=0, db y=0) { this->real=x, this->imag=y; }
inline cp operator + (const cp x) { return cp(this->real+x.real, this->imag+x.imag); }
inline cp operator - (const cp x) { return cp(this->real-x.real, this->imag-x.imag); }
inline cp operator * (const cp x) {
return cp(this->real*x.real-this->imag*x.imag, this->real*x.imag+this->imag*x.real);
}
};
cp x[N], y[N];
int n, m, nn, rev[N]; inline void fft(const int n, cp *a, const int k) {
for(int i=1;i<=n;i++) if(i<rev[i]) std::swap(a[i], a[rev[i]]); for(int len=2;len<=n;len<<=1) {
cp omega_n(cos(2*PI/len), sin(2*PI*k/len));
int m=len>>1;
for(int i=0;i<n;i+=len) {
cp omega(1, 0);
for(int j=i;j<i+m;j++) {
cp t1=a[j], t2=omega*a[j+m];
a[j]=t1+t2, a[j+m]=t1-t2;
omega=omega*omega_n;
}
}
} if(k==-1)
for(int i=0;i<n;i++) a[i].real=(int)(a[i].real/n+0.5);
} int main() {
n=in(), m=in();
for(int i=0;i<=n;i++) x[i].real=in();
for(int i=0;i<=m;i++) y[i].real=in();
m+=n, nn=1;
while(nn<=m) nn<<=1;
for(int i=1;i<nn;i++) rev[i]=(rev[i>>1]>>1)|(i&1)*(nn>>1); fft(nn, x, 1); fft(nn, y, 1);
for(int i=0;i<nn;i++) x[i]=x[i]*y[i];
fft(nn, x, -1);
for(int i=0;i<=m;i++) out((int)(x[i].real));
putchar('\n');
return 0;
}

FFT学习笔记的更多相关文章

  1. 快速傅里叶变换(FFT)学习笔记

    定义 多项式 系数表示法 设\(A(x)\)表示一个\(n-1\)次多项式,则所有项的系数组成的\(n\)维向量\((a_0,a_1,a_2,\dots,a_{n-1})\)唯一确定了这个多项式. 即 ...

  2. 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT)

    再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Bluestein算法+分治FFT+FFT的优化+任意模数NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其三)(循环卷积的Blueste ...

  3. 快速傅里叶变换(FFT)学习笔记(其一)

    再探快速傅里叶变换(FFT)学习笔记(其一) 目录 再探快速傅里叶变换(FFT)学习笔记(其一) 写在前面 为什么写这篇博客 一些约定 前置知识 多项式卷积 多项式的系数表达式和点值表达式 单位根及其 ...

  4. 快速傅里叶变换(FFT)学习笔记(其二)(NTT)

    再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 目录 再探快速傅里叶变换(FFT)学习笔记(其二)(NTT) 写在前面 一些约定 前置知识 同余类和剩余系 欧拉定理 阶 原根 求原根 NTT ...

  5. 口胡FFT现场(没准就听懂了)&&FFT学习笔记

    前言(不想听的可以跳到下面) OK.蒟蒻又来口胡了. 自从ZJOI2019上Day的数论课上的多项式听到懵逼了,所以我就下定决心要学好多项式.感觉自己以前学的多项式都是假的. 但是一直在咕咕,现在是中 ...

  6. 【笔记篇】(理论向)快速傅里叶变换(FFT)学习笔记w

    现在真是一碰电脑就很颓废啊... 于是早晨把电脑锁上然后在旁边啃了一节课多的算导, 把FFT的基本原理整明白了.. 但是我并不觉得自己能讲明白... Fast Fourier Transformati ...

  7. 多项式乘法(FFT)学习笔记

    ------------------------------------------本文只探讨多项式乘法(FFT)在信息学中的应用如有错误或不明欢迎指出或提问,在此不胜感激 多项式 1.系数表示法  ...

  8. 【文文殿下】快速傅里叶变换(FFT)学习笔记

    多项式 定义 形如\(A(x)=\sum_{i=0}^{n-1} a_i x^i\)的式子称为多项式. 我们把\(n\)称为该多项式的次数界. 显然,一个\(n-1\)次多项式的次数界为\(n\). ...

  9. 分治 FFT学习笔记

    先给一道luogu板子题:P4721 [模板]分治 FFT 今天模拟有道题的部分分做法是分治fft,于是就学了一下.感觉不是很难,国赛上如果推出式子的话应该能写出来. 分治fft用来解决这么一个式子\ ...

随机推荐

  1. MVC 全局过滤器

    1. 新创建一个类 CheckLogin2. 在类中加入以下代码 public class CheckLogin : ActionFilterAttribute { public override v ...

  2. mysql-笔记-函数

    如果不指定:sql-mode=ignore_space ,函数名与后面的括号之前不能有空格

  3. PHP7中的数据类型

    PHP中变量名→zval,变量值→zend_value.其变量内存是通过引用计数管理的,在PHP7中引用计数在value结构中. 变量类型: 头文件在PHP源码 /zend/zend_types.h ...

  4. hdu-1711(hash)

    题意:给你T组数据,每组数据分别输入n,m和长度为n的数字数组,和长度为m的数字数组,问你长度为m的数组第一次出现在长度为n的数组的位置 解题思路:标准字符串匹配问题,一般用kmp解,拿来练hash ...

  5. 使用Excel批量提取文件名

    Excel中如何快速选择所有奇数行或偶数行? 方法3:=MOD(ROW(),2) 总结:方法1和方法2的区别主要在ctrl键的使用 轻松将一个文件夹里所有的文件名提取出来,放到Excel表格里 &qu ...

  6. TOP按钮

    TOP按钮 博客园页面添加返回顶部TOP按钮 进入网页管理->设置 在"页面定制CSS代码"中添加如下css样式,当然你可以改为自己喜欢的样式 此处可以将背景色backgro ...

  7. 执行Git命令时出现各种 SSL certificate problem 的解决办法

    执行Git命令时出现各种 SSL certificate problem 的解决办法 来源  https://www.cnblogs.com/chenzc/p/5842932.html 比如我在win ...

  8. 一本通 一笔画问题 洛谷P1636 Einstein学画画

    P1636 Einstein学画画 相信大家都玩过一笔画这种游戏吧,这其实算得上是我们能够接触到的比较常见的数学问题,有一个很知名的就是七桥问题 这个问题包括所有的一笔画问题都是在欧拉回路的涵盖范围内 ...

  9. MySQL架构备份之双机热备

    M--S架构:实现双机热备(AB复制) 1.可以降低master读压力 2.可以对数据库做“热备”,热备只能解决硬件master硬件故障,软件故障等重大故障问题,但无法解决人为误操作导致的逻辑故障(列 ...

  10. TCPDUMP 使用教程

    TCPDUMP 命令使用简介 简单介绍 tcpdump 是一款强大的网络抓包工具,运行在 Linux 平台上.熟悉 tcpdump 的使用能够帮助你分析.调试网络数据. 要想很好地掌握 tcpdump ...