FFT学习笔记
DFT:离散傅里叶变换
FFT :快速傅里叶变换 用于求多项式乘法O(nlog(n))
FNTT/NTT :FTT的优化,常数及精度更优
FWT:快速沃尔什变换
MTT:任意模数NTT
FMT 快速莫比乌斯变换
SFT 稀疏傅里叶变换 百度百科说比FFT快10到100倍但是在OI里真的有应用吗?
后面三个我都不会,这里也不介绍 NTT我也不会,仙姑了~
以下是一些前置知识(当然不只是前置知识🌚)
多项式
单位根
快速傅里叶变换
放个码
struct complex{
double x,y;
complex operator+(const complex op)const{
return complex{x+op.x,y+op.y};
}
complex operator-(const complex op)const{
return complex{x-op.x,y-op.y};
}
complex operator*(const complex op)const{
return complex{x*op.x-y*op.y,x*op.y+y*op.x};
}
};//当然你也可以直接用std里的complex
void FFT(complex*a,int n,int op){
if(!n)return;
complex a1[n],a2[n];
for(int i=0;i<n;++i)a1[i]=a[i<<1],a2[i]=a[i<<1|1];
FFT(a1,n>>1,op);FFT(a2,n>>1,op);
complex W{cos(pi/n),sin(pi/n)*op},w{1,0};
for(int i=0;i<n;++i,w=w*W)a[i]=a1[i]+w*a2[i],a[i+n]=a1[i]-w*a2[i];
}
快速傅里叶逆变换
怕有人没想过来还是写一下🌚。
我也不知道 可以去学一下用范德蒙德矩阵推导,然后就知道为什么是这个了🌚。
点击查看代码
#include<bits/stdc++.h>
const int N=4e6+10;
const double pi=acos(-1);
struct complex{
double x,y;
complex operator+(const complex op)const{
return complex{x+op.x,y+op.y};
}
complex operator-(const complex op)const{
return complex{x-op.x,y-op.y};
}
complex operator*(const complex op)const{
return complex{x*op.x-y*op.y,x*op.y+y*op.x};
}
}a[N],b[N];
int n,m;
inline int read(){
int ans=0;char ch=getchar_unlocked();bool fl=0;
while(ch<'0'||ch>'9'){if(ch=='-')fl=1;ch=getchar_unlocked();}
while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar_unlocked();
return fl?~ans+1:ans;
}
inline void print(int x){(x<0)&&(putchar_unlocked('-'),x=-x);(x>9)&&(print(x/10),0);putchar_unlocked(x%10|48);}
void FFT(complex*a,int n,int op){
if(!n)return;
complex a0[n],a1[n];
for(int i=0;i<n;++i)a0[i]=a[i<<1],a1[i]=a[i<<1|1];
FFT(a0,n>>1,op);FFT(a1,n>>1,op);
complex W{cos(pi/n),sin(pi/n)*op},w{1,0};
for(int i=0;i<n;++i,w=w*W)a[i]=a0[i]+w*a1[i],a[i+n]=a0[i]-w*a1[i];
}
int main(){
// freopen("1.in","r",stdin);
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)a[i]={1.0*read(),0};
for(int i=0;i<=m;++i)b[i]={1.0*read(),0};
m=m+n;n=1;
while(n<=m)n<<=1;
FFT(a,n>>1,1);FFT(b,n>>1,1);
for(int i=0;i<n;++i)a[i]=a[i]*b[i];
FFT(a,n>>1,-1);
for(int i=0;i<=m;++i)printf("%.0f ",fabs(a[i].x)/n);
}
a[i]=a1[i]+w*a2[i],a[i+n]=a1[i]-w*a2[i];
,其中 w*a2[i]
我们计算了两次,而复数相乘常数会很大,所以我们拿个变量记一下就好了。于是我们完成了所有的优化
位逆序置换
证明我也不知道
非递归版FFT
#include<bits/stdc++.h>
const int N=4e6+10;
const double pi=acos(-1);
struct complex{
double x,y;
complex operator+(const complex op)const{
return complex{x+op.x,y+op.y};
}
complex operator-(const complex op)const{
return complex{x-op.x,y-op.y};
}
complex operator*(const complex op)const{
return complex{x*op.x-y*op.y,x*op.y+y*op.x};
}
}a[N],b[N],buf[N];
int n,m,pos[N];
inline int read(){
int ans=0;char ch=getchar_unlocked();bool fl=0;
while(ch<'0'||ch>'9'){if(ch=='-')fl=1;ch=getchar_unlocked();}
while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar_unlocked();
return fl?~ans+1:ans;
}
inline void print(int x){(x<0)&&(putchar_unlocked('-'),x=-x);(x>9)&&(print(x/10),0);putchar_unlocked(x%10|48);}
void FFT(complex a[],int flag){
for(int i=0;i<n;i++)if(i<pos[i])std::swap(a[i],a[pos[i]]);
for(int mid=1;mid<n;mid<<=1){
complex W={cos(pi/mid),flag*sin(pi/mid)};
for(int r=mid<<1,j=0;j<n;j+=r){
complex w={1,0};
for(int k=0;k<mid;k++,w=w*W){
complex op=w*a[j+k+mid];
buf[j+k]=a[j+k]+op;
buf[j+k+mid]=a[j+k]-op;
}
}
for(int i=0;i<n;i++)a[i]=buf[i];
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)a[i]={1.0*read(),0};
for(int i=0;i<=m;++i)b[i]={1.0*read(),0};
m=m+n;n=1;
while(n<=m)n<<=1;
for(int i=1;i<=n;i++)pos[i]=(pos[i>>1]>>1)|((i&1)*(n>>1));
FFT(a,1);FFT(b,1);
for(int i=0;i<n;++i)a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=m;++i)printf("%.0f ",fabs(a[i].x)/n);
}
我也不知道,但理论上是快了🌚,大概是我写的常数太大了吧🌚。
我们认为它起到了一定的优化作用,但是我们发现多了一个数组,那我们可不可以不要这个数组呢?于是就有了下面这个优化:
蝶形运算优化
for(int r=mid<<1,j=0;j<n;j+=r){
complex w={1,0};
for(int k=0;k<mid;k++,w=w*W){
complex op=w*a[j+k+mid];
buf[j+k]=a[j+k]+op;
buf[j+k+mid]=a[j+k]-op;
}
}
for(int i=0;i<n;i++)a[i]=buf[i];
就是这样
#include<bits/stdc++.h>
const int N=4e6+10;
const double pi=acos(-1);
struct complex{
double x,y;
complex operator+(const complex op)const{
return complex{x+op.x,y+op.y};
}
complex operator-(const complex op)const{
return complex{x-op.x,y-op.y};
}
complex operator*(const complex op)const{
return complex{x*op.x-y*op.y,x*op.y+y*op.x};
}
}a[N],b[N];
int n,m,pos[N];
inline int read(){
int ans=0;char ch=getchar_unlocked();bool fl=0;
while(ch<'0'||ch>'9'){if(ch=='-')fl=1;ch=getchar_unlocked();}
while(ch>='0'&&ch<='9')ans=(ans<<3)+(ans<<1)+(ch^48),ch=getchar_unlocked();
return fl?~ans+1:ans;
}
inline void print(int x){(x<0)&&(putchar_unlocked('-'),x=-x);(x>9)&&(print(x/10),0);putchar_unlocked(x%10|48);}
void FFT(complex a[],int flag){
for(int i=0;i<n;i++)if(i<pos[i])std::swap(a[i],a[pos[i]]);
for(int mid=1;mid<n;mid<<=1){
complex W={cos(pi/mid),flag*sin(pi/mid)};
for(int r=mid<<1,j=0;j<n;j+=r){
complex w={1,0};
for(int k=0;k<mid;k++,w=w*W){
complex op=w*a[j+k+mid],opl=a[j+k];
a[j+k]=opl+op;
a[j+k+mid]=opl-op;
}
}
}
}
int main(){
scanf("%d%d",&n,&m);
for(int i=0;i<=n;++i)a[i]={1.0*read(),0};
for(int i=0;i<=m;++i)b[i]={1.0*read(),0};
m=m+n;n=1;
while(n<=m)n<<=1;
for(int i=1;i<=n;i++)pos[i]=(pos[i>>1]>>1)|((i&1)*(n>>1));
FFT(a,1);FFT(b,1);
for(int i=0;i<n;++i)a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0;i<=m;++i)printf("%.0f ",fabs(a[i].x)/n);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?