蒟蒻数论笔记:讲给数论小白的快速傅里叶变换

题外话

我感觉自己的数论笔记从来没有写完过,从极限到微积分到积型函数到FFT

2021.11.10 为啥过了一个晚上就有15个人看了???

正题

1.傅里叶变换:

有关傅里叶变换,##先看视频##

2.前置芝士之泰勒展开

(这里不理解也没有什么问题,知道结论即可)

泰勒展开的本质就是用一个多项式来拟合一个函数在x0处的变化,为此要做到每一次求导都与原函数相同,以此来拟合函数的变化。

g(x)=f(x0)+f(x0)(xx0)+f(xx0)22!+f(xx0)33!++fn(xx0)nn!

分母是幂函数求导带来的常数项约去

所以,我们可以用它来展开几个函数:eix,isin(x),cos(x)其中i1

已知:

ex=0(xx0)nn!

拓展到复数:

ez=n=0znn!,zC

放缩:

eix=1+ixx22!ix33!+x44!+ix55!+

已知:

sin(x)=xx33!+x55!x77!+

乘上一个复数:

isin(x)=ix1!ix33!+ix55!ix77!+

已知:

cos(x)=11x22!+x44!x66!+

得出:

eix=isin(x)+cos(x)

也就是欧拉公式。

带入π得:

eiπ+1=0

可以理解为,随着x的增大,eix也在一个圆上运动。

于是结合上面的视频可以理解傅里叶变换的公式:
这里变成了向量

+g(t)e2πiftdt

加负号是因为变换旋转的方向。

普通的傅里叶是O(n2)的,而快速傅里叶可以做到O(nlogn)

2.前置芝士之复数运算

看到前面的复数运算,有些同学可能已经有点搞不懂了,我一开始也是这样的。

这里就简单讲一下复数的运算,就是数学选修2-2的最后一章,(可以自己看)。

简单来说,复数集是对实数集的一个扩充,在定义上,我们这样来看复数:

C={a+bi|a,bR}

我们把这个集合中的数,形如a+bi的叫做复数,i是虚数单位,C是复数集。

为了简洁,我们直接开始四则运算:

(a+bi)+(c+di)=(a+c)+(b+d)i

(a+bi)(c+di)=(ac)+(bd)i

(a+bi)×(c+di)=(acbd)+(ad+bc)i

(a+bi)÷(c+di)=ac+bdc2+d2+bcadc2+d2i

3.前置芝士之多项式卷积

先推荐一个回答

博主讲的很好。QWQ

像乘法,也可以变成多项式乘法,然后卷积。

h(k)=i+j=kf(i)g(j)

快乐的O(n2)
在FFT中,我们就是把两个多项式变成了两组向量,然后两组向量卷积求新的向量。

4.前置芝士之点乘法

n阶的函数由n+1个点唯一确定。

传统的多项式是用系数表示法,我们可以换为点值表示法来表示。
两个与x有关的多项式:

A(x)=(x0,y0),(x1,y1)(xn,yn)

B(x)=(x0,y0),(x1,y1)(xn,yn)

两个相加就直接加上即可,相乘的话比较麻烦。

因为C(x)的项数2n+1,所以两个式子都要补上一堆项,然后项多项式加法一样按位运算。

A(x)=(x0,y0),(x1,y1)(x2n,y2n)

B(x)=(x0,y0),(x1,y1)(x2n,y2n)

乘法:

A(x)B(x)=(x0,y0y0),(x1,y1y1),(x2n,y2ny2n)

现在我们见证了奇迹的诞生!!O(n)的时间复杂度!!!

所以,我们到现在就知道了FFT的流程:

1.得到两个多项式A,B;

2.对于每个多项式,选取n+1个点,得出点值表达(O(n2))

3.点乘法

4.将C(x)的点值表达式转化为系数表达式。O(n2)

5.前置芝士之拉格朗日插值

虽然没有用,但是还是要讲一下。

插值主要用在将点值表达式转换为系数表达式上,也就是一个n阶的函数由n+1个不同的点唯一确定。

常见的插值法是拉格朗日插值法。

由于有n个点,求n-1阶函数。我们先枚举每一个点xi,建立一个函数fi(x),使得fi(xi)=yi,而其他的xj,j!=i带入,fi(xj)=0

然后,一整个函数就是由这n个函数累加起来的和,在没一个点xi上的值都对应其yi

然后构造出公式:

f(x)=i=0n1yi×j!=ixxjxixj

这就可以用来完成FFT的第三步,但是时间复杂度是O(n2)。FFT继续优化。

读到这里结合前面的芝士,你已经知道了DFT以及IDFT了,接下来就是FFT的诞生之路

FFT优化之路

6.前置芝士之单位复根

n次的单位复根是满足ωn=1的复数ω,因为ωn在复数平面上的模都是一,所以相乘之后还是一,那么所有的ωi都会均匀度分布在单位圆上,n=8时;

我们再考虑欧拉公式:

eix=cosx+isinx

x=2π

e2πi=1=ωn

从而:

ω=e2πin

把此时的单位根为主次单位根:

ωn=e2πin

对于其他单位根,

ωnk=e2πikn,0k<n

第一步的优化:快速傅里叶变换之O(nlogn)求点值

对一个多项式求点值,我们带入2n个点求点值,并强选2t个单位根,保证n为2的整数次幂,那么把2t带为n。

得:

ωnk=e2πikn,0k<n

有:

ωn(j+k)%n=ωnj×ωnk

所以多项式A(x)的点值为:

A(ωn0)=a0+a1+a2+a3++an1A(ωn1)=a0+a1ωn1+a2ωn2+a3ωn4++an1ωnn1A(ωn2)=a0+a1ωn2+a2ωn4+a3ωn6++an1ωn2n2

考虑把每个点值按照奇偶分开:

A(ωni)=(a0+a2ωn2i+a4ωn4i+a6ωn6i++an2ωni(n2))+(a1ωni+a3ωn3i+a5ωn5i++an1ωn1(n1)i)

左边提一个ωni

A(ωni)=(a0+a2ωn2i+a4ωn4i+a6ωn6i++an2ωni(n2))+ωni(a1+a3ωn2i+a5ωn4i++an1ωn1(n2)i)

然后,把左边变成一个系数为a0,a2,a4,a6的多项式FL(x),带入ωn2i,也就是ωn/2i(看上面的芝士即可懂了)。

把右边也变成一个系数为a1,a3,a5,a7的多项式FR(x),带入ωn/2i
得:

A(ωni)=FL(ωn/2i)+ωniFR(ωn/2i)

n=8时:

A(ωn0)=FL(ωn/20)+ωn0FR(ωn/20)

A(ωn1)=FL(ωn/21)+ωn1FR(ωn/21)

A(ωn2)=FL(ωn/22)+ωn2FR(ωn/22)

A(ωn3)=FL(ωn/23)+ωn3FR(ωn/23)

A(ωn4)=FL(ωn/20)+ωn4FR(ωn/20)

A(ωn5)=FL(ωn/21)+ωn5FR(ωn/21)

A(ωn6)=FL(ωn/22)+ωn6FR(ωn/22)

A(ωn7)=FL(ωn/23)+ωn7FR(ωn/23)

这时我们发现前后的值是一样的,就可以折半计算了:
T(N)=2T(N2)+O(n)=O(nlogn)
啊啊啊啊!!冯巨讲的时候好迷茫现在看来真NB!!

第三步的优化:快速傅里叶逆变换之O(nlogn)求系数

在前置芝士之拉格朗日插值中,我们在求了C(x)的点值表达式后,运用拉格朗日插值法,O(n2)求出系数表达式。

在这里,我们可以用快速傅里叶变换的优秀性质来进行逆变换。
首先,多项式的点值

A(ωnx)=i=1n1aiωnxi

我们用bi来表示上面的式子进行快速傅里叶变换后的A(ωni)

我们对这些bi又进行一次快速傅里叶变换:

C(ωnx)=i=1n1biωnxi

恒等变换:

C(ωnx)=i=1n1ωnxij=1n1ajωnij

基本操作交换sum:

C(ωnx)=j=0n1i=0n1ωni×(j+x)

由单位复根的性质得,当(j+x)=n时,j+x才有值,后面的才有意义。
所以:

C(ωn0)=na0

C(ωn1)=nan1

C(ωn2)=nan2

C(ωn3)=nan3

C(ωnn)=na1

所以这就得到了系数了,IFFT万岁!!!

后置甜点之代码

首先是递归版本,常数有点大(板子都过不了

转载大佬的伪代码:

点击查看代码
int Lim = 1, N, M ;
function FFT(int lenth, complex *A, int flag){
    IF (Lim == 1) return ;
    complex A0[lenth >> 1], A1[lenth >> 1] ;//分成两部分
    for(int j : 0 to lenth by_grow 2) A0[j >> 1] = A[j], A1[j >> 1] = A[j + 1] ;
    FFT(lenth >> 1, A0, flag) ;
    FFT(lenth >> 1, A1, flag) ;
    complex Wn = unit(,) , w = (1, 0) ;
  //Wn是单位根,w用来枚举幂,即我们令主次单位根不变,由于其余单位根都是其整次幂,所以可以用一个w来记录到第几次幂
  /*此处求单位根的时候会用到我们的参数flag……嗯没错就用这一次,并且flag的值域为(-1, 1)……是的,只会有两个值*/
    for(int j : 0 to (lenth >> 1) by_grow 1 with w = w * Wn){
        A[i] = A0[i] + A1[i] * w ;//应用公式,下同 
        A[i + (lenth >> 1)] = A0[i] - A1[i] * w ; //顺便求出另一半,由折半引理可显然。 
    } 
} 
function Main{
    input(N), input(M) ;
    for(i : 0 to N by_grow 1) => input(A) ;
    for(i : 0 to M by_grow 1) => input(B) ; 
    while(Lim < N + M) Lim <<= 1 ;//Lim为结果多项式的长度(暂时),化为2的幂的便于分治(二分)
    FFT(Lim, A, 1) ;//两遍FFT表示从系数化为点值 
    FFT(Lim, B, 1) ;
    for(i : 0 to Lim by_grow 2) => A[i] *= B[i] ;//点乘法,此处需要重定义乘号,因为每一项现在表示的是一个点,有x和y两个属性qwq 
}

cpp:

点击查看代码
void FFT(int Lim,complex *A,int flag){
    if(Lim == 1) return ;
    complex A0[Lim >> 1], A1[Lim >> 1] ;
    for(int i = 0; i <= Lim ; i += 2)
        A0[i >> 1] = A[i], A1[i >> 1] = A[i+1] ;
    FFT(Lim >> 1, A0, flag) ;
    FFT(Lim >> 1, A1, flag) ;
    complex unit = (complex){cos(2.0 * Pi / Lim) , flag * sin(2.0 * Pi / Lim)}, w = complex(1, 0) ;//欧拉公式 
    for(int i = 0;i < (Lim >> 1) ; i ++, w = w * unit) {
        A[i] = A0[i] + w * A1[i] ;
        A[i + (Lim>>1)] = A0[i] - w*A1[i];
    }
}

int main(){
......................
FFT(A, 1), FFT(B, 1) ;
for(i = 0; i <= Lim; i ++) A[i] = A[i] * B[i] ;
FFT(A, -1) ;
......................
}

最后

本蒟蒻的第一篇写完了的数论笔记。
后面来更FFT的优化。
感谢冯巨,
引用:https://www.cnblogs.com/pks-t/p/9251147.html
%%%%%%%%%%%%%%%%%%%%%%%%

posted @   SSZX_loser_lcy  阅读(314)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效
点击右上角即可分享
微信分享提示