FFT感性瞎扯
为了自己以后再用FFT时不再一脸懵X,本蒟蒻决定感性理解一下FFT。
FFT可以干啥?
把两个多项式乘在一起。具体地说,对于两个多项式和,得到一个多项式。
显然,如果暴力的话,是的。
但是,FFT可以做到!
我们一点一点把它扯个不明不白。
Part1.复数
只需要记住一点结论:
复数相乘时,模长相乘,辐角相加。
比如说,对于两个极角表示法的复数与,有
Part2.单位根
(以下,默认为的整数幂)
我们把单位圆上的某个复数称作单位根。换句话说,就是极角表示法下的复数。当然,因为是在单位圆上,我们可以只关注它的辐角。
如图。

结论1:。
类比正负角。可以感性理解一下,就等于绕了一整圈,度数的角。有
结论2:
等于绕了半圈的角。
结论3:当为偶数时,。这是最重要的结论。
有
Part3.DFT
显然,个点可以唯一确定一个次多项式。
而我们如果知道和上各个点,就可以在时间內把它们乘在一起。对于每个点与,新点即为。
DFT解决的就是将多项式由系数表达转为点值表达。
开始推式子:
令,
则。
令。
则
再令。
则
注意到什么了吗?
**与,结果只有最后一项的符号不同!!!!!!**
然后就可以分治了。只要知道与的值,就可以直接得出与!!!
因此我们可以令成为新的,进一步递推。
Part4.IDFT
只需要将DFT中的改为即可。别忘了最后记得除上数组长度!!!
Part5.代码:
#include<bits/stdc++.h> using namespace std; const int MAXN=4000000; int n,m,lim=1,t,res[MAXN]; const double pi=acos(-1); struct cp{ double x,y; cp(double u=0.0,double v=0.0){ x=u,y=v; } friend cp operator +(const cp &lv,const cp &rv){ return cp(lv.x+rv.x,lv.y+rv.y); } friend cp operator -(const cp &lv,const cp &rv){ return cp(lv.x-rv.x,lv.y-rv.y); } friend cp operator *(const cp &lv,const cp &rv){ return cp(lv.x*rv.x-lv.y*rv.y,lv.x*rv.y+lv.y*rv.x); } }f[MAXN],g[MAXN]; void FFT(cp *a,int sz,int inv){ if(sz==1)return; int md=sz>>1; static cp b[MAXN]; for(int i=0;i<md;i++)b[i]=a[i<<1],b[i+md]=a[(i<<1)+1]; for(int i=0;i<sz;i++)a[i]=b[i]; FFT(a,md,inv),FFT(a+md,md,inv); for(int i=0;i<md;i++){ cp x=cp(cos(2*pi*i/sz),inv*sin(2*pi*i/sz)); b[i]=a[i]+x*a[i+md],b[i+md]=a[i]-x*a[i+md]; } for(int i=0;i<sz;i++)a[i]=b[i]; } int read(){ char c=getchar(); int x=0; while(c>'9'||c<'0')c=getchar(); while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x; } int main(){ n=read(),m=read(); for(int i=0,t;i<=n;i++)f[i].x=read(); for(int i=0,t;i<=m;i++)g[i].x=read(); while(lim<=n+m)lim<<=1; FFT(f,lim,1),FFT(g,lim,1); for(int i=0;i<lim;i++)f[i]=f[i]*g[i]; FFT(f,lim,-1); for(int i=0;i<lim;i++)res[i]=(int)(f[i].x/lim+0.5); for(int i=0;i<=n+m;i++)printf("%d ",res[i]); return 0; }
Part6.非递归
在FFT时,一个位置上的数的最终位置,是把其位置的二进制表达翻转后的新位置。
即有:
显然,当长度不同时,一个数可能翻转到不同的位置。
而它的终点位置,可以如此递推:
for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1));
其中,是长度,。
Part7.最终非递归代码:
#include<bits/stdc++.h> using namespace std; const int MAXN=4000000; int n,m,lim=1,t,res[MAXN],lg,rev[MAXN]; const double pi=acos(-1); struct cp{ double x,y; cp(double u=0.0,double v=0.0){ x=u,y=v; } friend cp operator +(const cp &lv,const cp &rv){ return cp(lv.x+rv.x,lv.y+rv.y); } friend cp operator -(const cp &lv,const cp &rv){ return cp(lv.x-rv.x,lv.y-rv.y); } friend cp operator *(const cp &lv,const cp &rv){ return cp(lv.x*rv.x-lv.y*rv.y,lv.x*rv.y+lv.y*rv.x); } }f[MAXN],g[MAXN]; int read(){ char c=getchar(); int x=0; while(c>'9'||c<'0')c=getchar(); while(c>='0'&&c<='9')x=(x<<3)+(x<<1)+(c^48),c=getchar(); return x; } void FFT(cp *a,int tp){ for(int i=0;i<lim;i++)if(i<rev[i])swap(a[i],a[rev[i]]); for(int md=1;md<lim;md<<=1){ cp rt=cp(cos(pi/md),tp*sin(pi/md)); for(int stp=md<<1,pos=0;pos<lim;pos+=stp){ cp w=cp(1,0); for(int i=0;i<md;i++,w=w*rt){ cp x=a[pos+i],y=w*a[pos+md+i]; a[pos+i]=x+y; a[pos+md+i]=x-y; } } } } int main(){ n=read(),m=read(); while(lim<=(n+m))lim<<=1,lg++; for(int i=0;i<lim;i++)rev[i]=(rev[i>>1]>>1)|((i&1)<<(lg-1)); for(int i=0;i<=n;i++)f[i].x=read(); for(int i=0;i<=m;i++)g[i].x=read(); FFT(f,1),FFT(g,1); for(int i=0;i<lim;i++)f[i]=f[i]*g[i]; FFT(f,-1); for(int i=0;i<lim;i++)res[i]=(int)(f[i].x/lim+0.5); for(int i=0;i<=n+m;i++)printf("%d ",res[i]); return 0; }
完结撒傅里叶~~~

【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 【杂谈】分布式事务——高大上的无用知识?