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

写在前面

为什么写这篇博客

笔者去年暑假刚刚学习过FFT,NTT的一些基础应用。但当时对FFT和NTT的理解还不够深入。本博客参考2016年国家集训队论文中雅礼中学毛啸的《再探快速傅立叶变换》,对之前学习时的不足之处做了补充。

为了不使篇幅过长,预计将把学习笔记分为四部分:

  1. DFT,IDFT,FFT的定义,实现与证明
  2. NTT的实现与证明,任意模数NTT
  3. FFT的优化技巧
  4. 多项式相关操作

注意:本文为了严谨性可能会牺牲一些可读性

一些约定

  1. \([p(x)]=\begin{cases}1,p(x)为真 \\ 0,p(x)为假 \end{cases}\)

  2. 本文中序列的下标从0开始

  3. 若\(s\)是一个序列,\(|s|\)表示\(s\)的长度

  4. 若大写字母如\(F(x)\)表示一个多项式,那么对应的小写字母如\(f\)表示多项式的每一项系数,即\(F(x)=\sum_{i=0}^{n-1} f_ix^i\)

前置知识

多项式卷积

多项式\(A(x)=\sum_{i=0}^{n-1} a_ix^i\)(注意最高次项的指数为\(n-1\),至于为什么要这样定义见下文)

定义1.1 对于两个多项式\(A(x)=\sum_{i=0}^{n-1} a_ix_i,B(x)=\sum_{i=0}^{m-1} b_ix_i\),我们定义\(A(x)\)和\(B(x)\)的卷积为:

\[A(x) \cdot B(x)=\sum_{i=0}^{n+m-2} x^i\sum_{k=0}^i a_kb_{i-k}
\]

实际上我们只要考虑系数,也就是给出两个序列\(a,b\),求序列\(c\)使得$$c_i=\sum_{k=0}^i a_k b_{i-k} \tag{1.1}$$

直接计算卷积显然是\(O(n^2)\)的,FFT可以用来优化这个过程

多项式的系数表达式和点值表达式

系数表达式就是多项式中每一项的系数\(a_0,a_1 \dots a_{n-1}\)

我们知道,n个点可以确定一个n次多项式.那么,一个n次多项式\(F(x)\)也可以表示成这样的一个集合\(\{(x_0,f(x_0)),(x_1,f(x_1)),(x_2,f(x_2)) \dots (x_{n-1},f(x_{n-1}))\}\).比如多项式\(f(x)=x^2+x+1\)就可以表示成\(\{(1,3),(2,7),(-1,1)\}\)

这样有什么用呢?如果已知\(A(x)\)和\(B(x)\)的点值表达式,可以\(O(n)\)计算\(C(x)=A(x)B(x)\)的点值表达式

比如\(A(x)=\{(x_0,A(x_0)),(x_1,A(x_1)),(x_2,A(x_2)),....,(x_n,A(x_n))\}\)

\(B(x)=\{(x_0,B(x_0)),(x_1,B(x_1)),(x_2,B(x_2)),....,(x_n,B(x_n))\}\)

\(C(x)=A(x)\times B(x)=\{(x_0,A(x_0)\times B(x_0)),(x_1,A(x_1)\times B(x_1)),(x_2,A(x_2)\times B(x_2)),....,(x_n,A(x_n)\times B(x_n))\}\)

所以:我们可以在\(O(n)\)的时间内计算出两个点值表达式的乘积,只需要把对应点的\(y\)坐标相乘即可。但是,把系数表达式和点值表达式互相转化仍然需要\(O(n^2)\)时间.(每代入一个\(x\)都需要\(O(n)\)时间计算\(A(x)\))

单位根及其性质

定义1.2:\(n\)次单位根为满足:\(n\)是最小的满足\(\omega^n=1\)的复数\(\omega\)

容易发现,\(n\)次单位根有\(n\)个,他们分布在复平面上的单位圆上。(这里我们只讨论\(n\)为偶数的情况)

把单位圆\(n\)等分,圆上的每个分割点都对应一个单位根.从点\((1,0)\)开始,逆时针把这\(n\)个点从\(0\)开始编号,第\(k\)个点对应的复数记作\(w_n^k\),上图呈现的就是\(\omega_{16}^0 \text{~} \omega_{16}^{15}\)

容易发现\(\omega_n^k\)的幅角为\(\frac{2k\pi}{n}\).(一整个圆为\(2\pi \ \text{rad}\),分为\(n\)份,再取\(k\)份),因此,有:

\[\omega_n^k=\cos \frac{2k\pi}{n}+\text{i} \sin \frac{2k\pi}{n} \tag{1.2.1}
\]

单位根有如下的性质:

定理1.2.1 \(\forall p \in \mathbb{Z}, \omega_n^k=\omega_{pn}^{pk}\)

代数证明:

\(\omega_{pn}^{pk}=\cos \frac{2pk \pi}{pn}+ \text{i}\sin \frac{2pk\pi}{pn}=\cos \frac{2k\pi}{n}+\text{i} \sin \frac{2k\pi}{n}=\omega_n^k\)

几何证明:单位圆分成\(n\)份,数\(k\)次,和分成\(pn\)份,数\(pk\)次所代表复数是一样的

定理1.2.2 \(\omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^k\)

代数证明:

\[\begin{aligned} \omega_{n}^{k+\frac{n}{2}} &= \cos \frac{2(k+\frac{n}{2}\pi)}{n}+ \text{i} \sin \frac{2(k+\frac{n}{2}\pi)}{n}\\ &=\cos(\frac{2k \pi}{n}+\pi)+\text{i}\sin(\frac{2k \pi}{n}+\pi) \\&= -\cos \frac{2k\pi}{n}-\text{i} \sin \frac{2k\pi}{n}=-\omega_n^k\end{aligned}
\]

倒数第二步用到了三角函数的诱导公式

几何证明:

\(\frac{n}{2}\)代表半圈,所以这个式子的意思是从编号为\(k\)的复数开始走半圈,代表的复数和之前的正好相反(实部和虚部都相反)

定理1.2.3 \(\omega_n^p \times \omega_n^q=\omega_n^{p+q}\)

令\(\alpha=\frac{2p\pi}{n},\beta=\frac{2p\pi}{n}\)

则\(\omega_n^p=\cos \alpha+ \text{i} \sin \alpha,\omega_n^q=\cos \beta+ \text{i} \sin \beta\)

\[\begin{aligned}
\omega_{n}^{p} \times \omega_{n}^{q}&=(\cos \alpha \cos \beta-\sin \alpha \sin \beta)+i(\sin \alpha \cos \beta+\cos \alpha \sin \beta)\\ &=\cos (\alpha+\beta)+i \sin
(\alpha+\beta)\\&=\omega_{n}^{p+q}
\end{aligned}\]

证明用到了复数乘法和三角函数的和角公式.注意到定理1.2.2实际上是定理1.2.3的推论,证明留给读者。这个公式极为重要,在FFT的公式推导中会用到。

定理1.2.3的另一个推论,\(\omega_n^k=(\omega_n^1)^k\)

证明显然

定理1.2.4

\[\forall v \in \mathbb{N},\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{v k}=[v \bmod n=0]
\]

证明:

当\(v \bmod n =0\)时, \(\omega_{n}^{vk}=\cos\frac{2vk \pi}{n}+\text{i} \sin \frac{2vk \pi}{n}\)

​ 那么\(\frac{vk}{n}\)一定是整数,所以\(\cos\frac{2vk \pi}{n}=1,\sin \frac{2vk \pi}{n}=0,\omega_n^{vk}=1\)

​ 所以\(\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{v k}=\frac{1}{n} \times n =1\)

当\(v \mod n \neq 0\)时,容易发现\(\omega_n^v \neq 1\),

​ 那么上式实际上是一个等比数列,根据等比数列求和公式有:

​ $$\begin{aligned} \frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{v k}&=\frac{1-\omega_n^{vn}}{n(1-\omega_n^v)} \ &= \frac{1-\omega_1^v}{n(1-\omega_n^v)}\end{aligned}$$

​ 而\(\omega_1^v=\cos(2v\pi)+i\sin(2v \pi)=1\)

​ 因此\(\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{v k}=0\)

证毕.

这个定理会在IDFT的正确性证明中用到

DFT和IDFT

在上文中我们提到了多项式的系数表达式和点值表达式,并发现点值表达式可以简化卷积运算。

离散傅里叶变换(Discrete Fourier Transform,DFT):是把系数表达式转化为点值表达式的一种算法

逆离散傅里叶变换(Inverse Discrete Fourier Transform,IDFT):是把点值表达式转化为系数表达式的一种算法。

那么求\(A\)和\(B\)的卷积就可以先对\(A\)和\(B\)做DFT,再将\(A\)和\(B\)的点值表达式相乘得到\(C\),最后再对\(C\)做IDFT

DFT的过程

对于多项式\(A,B\),我们找到最小的大于\(\max(|A|,|B|)\)的2的整数幂\(n\),至于为什么是2的整数幂后面会提到。给定序列\(a\),对于\(k \notin [0,|a|)\),我们定义\(a_k=0\)

要想得到一个系数表达式的点值表达式,我们可以任意选\(n\)个值代入。但是傅里叶想到,单位根有着特殊的性质,可以把\(n\)次单位根代入多项式。形式化的说,设多项式\(A(x)=\sum_{i=0}^{n-1}a_ix^i\),则\(A(x)\)的DFT变换结果为\(a'_k=A(\omega_n^k)=\sum_{i=0}^{n-1}a_i \omega_n^{ki}\)

我们后面提到的多项式与系数序列的对应关系都类似\(A(x)\)与\(a\)的对应关系,对\(A(x)\)做DFT就是对序列\(a\)做DFT;说一个东西做DFT得到了\(A(x)\),就是得到了序列\(a\)

容易发现DFT的时间复杂度为\(O(n^2)\)

IDFT的过程

代入\(n\)次单位根的作用在IDFT中显现出来.

定理2.1 :把多项式\(A(x)\)的离散傅里叶变换结果\(f(\omega_n^0),f(\omega_n^1) \dots f(\omega_n^{n-1})\)作为另一个多项式\(B(x)\)的系数,取单位根的共轭复数即\(\omega_n^0,\omega_n^{-1},\omega_n^{-2} \dots \omega_n^{-(n-1)}\)作为\(x\)代入\(B(x)\),得到的每个数再除以\(n\),得到的是\(A(x)\)的各项系数

也就是说,把点值表达式中点的\(y\)坐标作为另一个多项式的系数后,IDFT的过程和DFT几乎一样,只是代入的是单位根的共轭复数。

我们暂时不证明定理2.1,而是证明一个更强的定理:

定理2.2:把多项式\(A(x)\)的DFT结果\(a\),和\(B(x)\)的DFT结果\(b\)对应相乘,得到序列\(t_i=a_ib_i\);再对\(t_i\)做IDFT,得到的序列就是\(C(x)=A(x)\cdot B(x)\)的各项系数\(c_i\)

证明:

由卷积的定义式\((1.1)\)我们有:

\[c_{r}=\sum_{p, q}[(p+q) \bmod n=r] a_{p} b_{q} \tag{2.1}
\]

看到取模想到定理1.1.4

\[\forall v \in \mathbb{N},\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{v k}=[v \bmod n=0] \tag{2.2}
\]

故:

\[\begin{aligned}
&[(p+q) \bmod n=r] \\
=&[(p+q-r) \bmod n=0] \\
=& \frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{(p+q-r) k} \\
=& \frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} \omega_n^{p k} \omega_n^{q k}
\end{aligned}\]

代入\((2.1)\)

\[\begin{align*}
c_{r} &=\sum_{p, q}[(p+q) \bmod n=r] a_{p} b_{q} \\
&=\sum_{p, q} \frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} \omega_n^{p k} \omega_n^{q k} a_{p} b_{q} \\
&=\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} \sum_{p, q} \omega_n^{p k} a_{p} \omega_n^{q k} b_{q} \\
&=\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} \sum_{p} \omega_n^{p k} a_{p} \sum_{q} \omega_n^{q k} b_{q}\tag{2.3} \\&=\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} a'_k b'_k \\&=\frac{1}{n} \sum_{k=0}^{n-1} \omega_n^{-r k} t_k\end{align*} \]

容易发现定理2.1定理2.2 的推论,即\(B(x)\)的系数都为1时的情况.式\((2.3)\)实际上指出了DFT和IDFT求卷积的过程。

和DFT类似,IDFT的时间复杂度为\(O(n^2)\)

FFT

上一部分我们提到了DFT的公式,但是这样直接做还是太慢了。因此我们要利用单位根的性质来加速DFT,这个算法被称为快速傅立叶变换(Fast Fourier Transform,FFT). 同样,我们也可以加速IDFT,即IFFT.

这几个算法的关系如下图所示:

其中FFT,IFFT的时间复杂度均为\(O(n \log n)\)

FFT的数学证明及时间复杂度分析

我们对多项式\(A(x) = a_0 + a_1x + a_2x^2 +...+a_{n-1}x^{n-1}\)进行FFT,仍然把\(\omega_n^k\)代入

把\(a\)按照下标奇偶性分为两部分:

\(A_1(x) = a_0 + a_2x + ... + a_{n - 2}x^{\frac{n}{2} - 1}\)

\(A_2(x) = a_1 + a_3x + ... + a_{n - 1}x^{\frac{n}{2} - 1}\)

那么我们有:

\(A(x) = A_1(x^2) + xA_2(x^2)\)

将\(x=\omega_n^k\)代入,

对于\(k < \frac{n}{2}\)(根据先前的定义,n为2的次幂,所以没有取整问题)

\[\begin{align*} A(\omega_n^k) &= A_1(\omega_n^{2k}) + \omega_n^kA_2(\omega_n^{2k}) \\ &= A_1(\omega_{\frac{n}{2}}^{k}) + \omega_n^kA_2(\omega_{\frac{n}{2}}^{k}) \tag{3.1}\end{align*}
\]

​ 证明用到了定理1.2.1:\(\forall p \in \mathbb{Z}, \omega_n^k=\omega_{pn}^{pk}\)

对于剩下的部分,即\(\omega_{n}^{k+\frac{n}{2}}(k<\frac{n}{2})\)

\[\begin{align*} A(\omega_n^{k + \frac{n}{2}}) &= A_1(\omega_n^{2k + n}) + \omega_n^{k + \frac{n}{2}}A_2(\omega_n^{2k + n}) \\ &= A_1(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) + \omega_n^{k + \frac{n}{2}} A_2(\omega_{\frac{n}{2}}^{k} \times \omega_n^n) \\ &= A_1(\omega_{\frac{n}{2}}^{k}) - \omega_n^kA_2(\omega_{\frac{n}{2}}^{k}) \tag{3.2} \end{align*}
\]

证明用到了定理1.2.1定理1.2.3:\(\omega_n^p \times \omega_n^q=\omega_n^{p+q}\)

式\((3.1),(3.2)\)也被称为“蝴蝶操作”, 通过蝴蝶操作,我们只要知道两个多项式\(A_1(x),A_2(x)\)在\((\omega_{\frac{n}{2}}^{0}, \omega_{\frac{n}{2}}^{1}, \omega_{\frac{n}{2}}^{2}, ... , \omega_{\frac{n}{2}}^{\frac{n}{2} - 1})\)处的点值表达式,就可以求出\(A(x)\)在\((\omega_n^0, \omega_n^1, \omega_n^2, ..., \omega_n^{n-1})\)处的点值表达式了了。每次递归下去规模缩小一半,\(n=1\)时终止.由于长度\(n\)每次除2,且要求每次的\(n\)均为偶数,所以我们需要\(n\)是2的正整数次幂

设给长度为\(n\)的序列做\(FFT\)的时间为\(T(n)\),那么我们有

\[T(n)=2T(\frac{n}{2})+n
\]

根据主定理,\(T(n)=\Theta(n \log n)\)

FFT的递归实现

FFT递归实现很好理解.注意可以使用C++自带的复数类<complex>,也可以自己手写复数类。

<complex>重载了+,-,*,/,=,==等运算符,在使用上很方便,几乎和操作整型变量没有区别。两个成员函数.real(),.imag()分别返回复数的实部和虚部。更多它的用法请参考这个链接

#include<complex>//c++自带复数类
const double pi=acos(-1.0);
typedef complex<double> com;
com a[maxn+5],b[maxn+5],c[maxn+5];
void fft(com *x,int n,int type){//orz Fourier
if(n==1) return;//递归边界
com l[n>>1],r[n>>1];
for(int i=0;i<n;i++){//按奇偶分类
if(!(i&1)) l[i>>1]=x[i];
else r[i>>1]=x[i];
}
fft(l,n>>1,type);//递归对n/2的长度做FFT
fft(r,n>>1,type); com wn1=com(cos(2*pi/n),type*sin(2*pi/n))/*w_n^0*/,wnk=com(1,0);
for(int i=0;i<(n>>1);i++){
x[i]=l[i]+wnk*r[i];//式(3.1)
x[i+(n>>1)]=l[i]-wnk*r[i];//式(3.2)
wnk*=wn1;//递推算w_n^k
}
}
int main(){
//....
int m=n*2;//这个数根据题目算要求来定,这里是长度为n的两个序列卷积,那就取2n
n=1;
while(n<m) n*=2;//计算出最小的2的次幂
fft(a,n,1);
fft(b,n,1);
for(int i=0;i<n;i++) c[i]=a[i]*b[i];
fft(c,n,-1);
for(int i=0;i<m;i++) printf("%lf ",c[i].real()/n);//记得除n
//....
}

FFT的非递归实现

很遗憾,FFT的递归实现常数极大(虽然非递归实现常数也很大),几乎无法通过任何OI中FFT的题目。

我们观察递归过程中根据奇偶改变系数位置的方式,例如长度为8的情况:

\[\begin{aligned}
&\left(a_{0}, a_{1}, a_{2}, a_{3}, a_{4}, a_{5}, a_{6}, a_{7}\right)\\
&\left(a_{0}, a_{2}, a_{4}, a_{6}\right)\left(a_{1}, a_{3}, a_{5}, a_{7}\right)\\
&\left(a_{0}, a_{4}\right)\left(a_{2}, a_{6}\right)\left(a_{1}, a_{5}\right)\left(a_{3}, a_{7}\right)\\
&\left(a_{0}\right)\left(a_{4}\right)\left(a_{2}\right)\left(a_{6}\right)\left(a_{1}\right)\left(a_{5}\right)\left(a_{3}\right)\left(a_{7}\right)
\end{aligned}\]

观察\(0,4,2,6,1,5,3,7\)的二进制表示\(000,100,010,110,001,101,011,111\),

比较\(0,1,2,3,4,5,6,7\)的二进制表示\(000,001,010,011,100,101,110,111\)

我们发现,初始时位置\(x\)上的数会被交换到x二进制位翻转后的位置,记为\(rev(x)\)注意这里的反转指的是反过来写,而不是每一位交换01。这种操作也被称为“位逆序置换”。那么只要处理出这个序列,每次FFT的都是一段连续的区间,可以非递归求解。

\(rev(x)\)可以根据如下代码递推求出

for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));

证明: 对于数\(i\),我们已经求出\(i>>1\)的置换结果,如\(k=5,i=(10111)_2,i>>1=(1011)_2\);那么我们先反转从左到右前\(k-1\)位,即\(rev[i>>1]=rev[(01011)_2]=(11010)_2\),右移一位去掉前导0(翻转后到了末尾)就得到了前\(k-1\)位反转的结果; 然后再加上最后一位,最后一位的值是\((i\text{&}1)\),要左移到最前面.

int rev[maxn+5];//数x位逆序置换后变成rev[x]
int n,k;//n=2^k
void fft(com *x,int n,int type){//type=1做FFT,type=-1做IFFT
for(int i=0;i<n;i++) if(i<rev[i]) swap(x[i],x[rev[i]]);//做置换,i<rev[i]防止一个数被换两次换回原位
for(int len=1;len<n;len*=2){
int sz=len*2;//当前处理的长度为len*2,len=1的情况不用计算,就是x[i]
com wn1=com(cos(2*pi/sz),sin(2*pi/sz)*type);//计算n次单位根w_n^1,type=-1时得到共轭复数
for(int l=0;l<n;l+=sz){//枚举一对长度为len的区间,对这两个区间做蝴蝶操作
int r=l+len-1;//[l,l+len-1]为前半段,[l+len,r]为后半段
com wnk=1;
for(int i=l;i<=r;i++){
com tmp=x[i+len];//用tmp临时存储值
x[i+len]=x[i]-wnk*tmp;//式(3.1)
x[i]=x[i]+wnk*tmp;//式(3.2)
wnk*=wn1;//递推计算w_n^k
}
}
}
if(type==-1) for(int i=0;i<n;i++) x[i]/=n;//IFFT记得除n
}
int main(){
n=1,k=0;
while(n<m){
n*=2;
k++;
}//预处理n,k
for(int i=0;i<n;i++) rev[i]=(rev[i>>1]>>1)|((i&1)<<(k-1));//预处理rev
fft(a,n,1);
fft(b,n,1);
for(int i=0;i<n;i++) ans[i]=a[i]*b[i];
fft(ans,n,-1);
}

FFT的局限性

显然FFT的过程中会出现浮点数的精度误差。比如在递推单位根\(w_n^k=w_n^{k-1}\times w_n^1\)时的精度误差比较大, 但一般可以接受。如果追求更高精度,可以对于每个\(k\)重新计算\(\omega_n^k=\cos \frac{2k\pi}{n}+\text{i} \sin \frac{2k\pi}{n}\),但时间开销较大。

如果有取模运算,可以使用第二节中提到的NTT算法

另外,在第三节中,我们会介绍减少FFT常数的几种方法,以及对任意长度的序列做DFT的方法。

例题

[1.BZOJ 3527] [ZJOI2014]力(FFT)

2.[Codeforces 580D]Fizzy Search(FFT)

快速傅里叶变换(FFT)学习笔记(其一)的更多相关文章

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

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

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

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

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

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

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

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

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

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

  6. 快速傅里叶变换FFT学习小记

    FFT学得还是有点模糊,原理那些基本还是算有所理解了吧,不过自己推这个推不动. 看的资料主要有这两个: http://blog.miskcoo.com/2015/04/polynomial-multi ...

  7. [学习笔记] 多项式与快速傅里叶变换(FFT)基础

    引入 可能有不少OIer都知道FFT这个神奇的算法, 通过一系列玄学的变化就可以在 $O(nlog(n))$ 的总时间复杂度内计算出两个向量的卷积, 而代码量却非常小. 博主一年半前曾经因COGS的一 ...

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

    [学习笔记]快速傅里叶变换 学习之前先看懂这个 浅谈范德蒙德(Vandermonde)方阵的逆矩阵的求法以及快速傅里叶变换(FFT)中IDFT的原理--gzy hhh开个玩笑. 讲一下\(FFT\) ...

  9. 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/常用套路【入门】

    原文链接https://www.cnblogs.com/zhouzhendong/p/Fast-Fourier-Transform.html 多项式 之 快速傅里叶变换(FFT)/数论变换(NTT)/ ...

随机推荐

  1. Python3基础——序列类型

    开头写给自己,To Myself: 很久以来,都想要学习一门编程语言,从去年选择了python开始,反反复复重新开始了N多遍,每一次不会超过俩星期.昨天无意间翻开自己去年记的学习笔记,不禁感叹想当年我 ...

  2. 二进制K8S集群使用Bootstrap Token 方式增加Node

    TLS Bootstraping:在kubernetes集群中,Node上组件kebelet和kube-proxy都需要与kube-apiserver进行通信,为了增加传输安全性,采用https方式, ...

  3. CentOS7.7 系统下 virbr0 虚拟网卡的维护与管理

    在 CentOS 7 系统的安装过程中,如果有选择相关虚拟化的的服务安装系统后,启动网卡时会发现有一个以网桥连接的私网地址的 virbr0 网卡,这个是因为在虚拟化中有使用到 libvirtd 服务生 ...

  4. 拜托,别再问我怎么自学 Java 了!和盘托出

    假如有那么残酷的一天,我不小心喝错了一瓶药,一下子抹掉了我这十多年的编程经验,把我变成了一只小白.我想自学 Java,并且想要找到一份工作,我预计需要 6 个月的时间,前提条件是每天都处于高效率的学习 ...

  5. antd pro table中的文件上传

    概述 示例代码 列表页面 form 页面 model.js service.js 总结 概述 项目中经常会遇到在表格中展示图片的需求(比如展示用户信息时, 有一列是用户的头像). antd pro t ...

  6. volatile、ThreadLocal的使用场景和原理

    并发编程中的三个概念 原子性 一个或多个操作.要么全部执行完成并且执行过程不会被打断,要么不执行.最常见的例子:i++/i--操作.不是原子性操作,如果不做好同步性就容易造成线程安全问题. 可见性 多 ...

  7. 多测师_肖sir _python 练习题(一)100以内奇数,偶数,质数胡计算

    (1)求1~100的和方法: 方法一:print(sum(range(1,101))) 方法二: sum1 = 0 i = 1 while True: sum1 = sum1 + i if i == ...

  8. python算法常用技巧与内置库

    python算法常用技巧与内置库 近些年随着python的越来越火,python也渐渐成为了很多程序员的喜爱.许多程序员已经开始使用python作为第一语言来刷题. 最近我在用python刷题的时候想 ...

  9. AT一万亿港元市值之差,腾讯和阿里到底“差”在哪里?

    很少有人注意到,港股之王已经悄然易主. 2019年底,阿里巴巴赴港二次上市,中国两大互联网巨头终于有了统一的比较口径,同台竞技之下,我们发现一个惊人事实--截止9月5日,港股阿里巴巴市值5.9万亿港元 ...

  10. centos8平台使用loginctl管理登录用户与session

    一,loginctl的用途: 控制 systemd 登录管理器 管理当前登录的用户和session 说明:刘宏缔的架构森林是一个专注架构的博客,地址:https://www.cnblogs.com/a ...