FFT
FFT
1.前言
FFT 要涉及很多前置基本概念,例如向量,复数等,在这里向量等简单概念不提。
2.1 复数
设 为实数, ,如果一个数 ,满足 的数叫复数,其中 为实部, 为虚部, 为虚数单位,当 时,称 为实数;当 ,且 时,称 为纯虚数。
2.2 复数域
复数域是实数域的代数闭包,即任何复系数多项式在复数域中总有根。
2.3 复平面
复平面是的每个点都表示一个复数,复数 在复平面中对应的坐标为
在复平面中, 轴又称为实轴; 轴又称为虚轴
轴上有且仅有一个实点即为原点
复数集 和复平面内所有点所构成的集合是一一对应的。
2.4 复数模
复数的实部与虚部的平方和的算术平方根称为该复数的模,记作
即对于复数 ,它的模为
复数 在复平面中的坐标为 ,到原点的距离正好是
2.5 幅角
任意一个复数 ,都可以写成
其中 , 为 的幅角,记做
任意一个非 的复数的幅角有无限多个取值,且这些值相差 的整数倍。
我们把满足 的辐角 的值,叫做辐角的主值,记作 , 是唯一的。
如果放到复平面中,复数的幅角可以看作从 轴正半轴到复数的转角
2.6 复数运算
2.6.1 平行四边形定则
复数的加法和向量的加法都满足平行四边形定则
2.6.2 加法
设复数
则
2.6.1.1 乘法---代数定义
设复数
则
2.6.1.2 乘法---几何意义
复数相乘,模长相乘,幅角相加
设复数
和 分别为 、 的模
则
3.1 单位根
在复平面中,以原点 为圆心, 为半径作圆,将所作的圆称为单位圆
将圆 等分,并将每个等分点标号
将 等分后的 号点记做 ,称为 次单位根
3.2 性质
性质1 : 设 等分后的 号点的值为 ,则有
性质2 :
性质3 :
性质4 :
性质5 :
4. 快速傅里叶 FFT
先再来了解了解多项式。
我们设定 为 , 为整数,不足则在多项式后面补
设一个 次多项式
易知
设 :
容易得出
假设 ,将 代入,得
将 代入,得
我们进行对比:
两者之差一个符号。
所以去在求 的时候可以同时 算出
第一个式子,在 取遍 时,第二个式子正好取遍
所以原问题将缩小一半,子问题还是满足原问题的性质。时空复杂度
代码实现最后说
5.快速傅里叶逆变换 IFFT
我们现在得到的是得到的是点值表示法下的乘积,但我们平时用系数表示的多项式,现在考虑快速地将点值表示法转换成系数表示法。
这个过程叫做傅里叶逆变换
是有一个结论的,由于这个结论过于复杂,我没听懂,不会证明。所以就背了算了。。。。
设一个
那么
6.IFFT 代码实现部分
快速傅里叶逆变换
typedef struct complex{
double a,b;
complex(double x=0,double y=0){a=x,b=y;}
complex operator+(const complex&t)const{return complex(a+t.a,b+t.b);}
complex operator-(const complex&t)const{return complex(a-t.a,b-t.b);}
complex operator*(const complex&t)const{return complex(a*t.a-b*t.b,a*t.b+b*t.a);}
}fastcomplex;
void FFT(fastcomplex a[],int n){
if(n==1) return ;
fastcomplex t[N];
int m=n>>1;
for(rint i=0;i<m;i++){
t[i]=a[i<<1];
t[i+m]=a[i<<1|1];
}
FFT(t,m),FFT(t+m,m);
for(rint i=0,j=m;i<m;i++,j++){
fastcomplex x(cos(2*pi*i/n),-sin(2*pi*i/n));
a[i]=t[i]+x*t[j];
a[j]=t[i]-x*t[j];
}
return ;
}
7.FFT 迭代实现——蝴蝶变化
观察下我们 FFT 中分治得到的结果,下面以 为例
对比我们的原序列和操作之后得到的序列下标,并用二进制表示:
原序列:000,001,010,011,100,101,110,111
后序列:000,100,010,110,001,101,011,111
每个位置分治后的最终位置,为其二进制位翻转后得到的位置
同 IFFT ,这个东西我不会证明,,,,,背了算了
通过这个规律,可以 处理出每个数的二进制表示翻转之后的结果
对于一个数 ,我们将其二进制表示记做 ,考虑如何求
假设我们已经算出来了
若 是偶数,那么 f[i]=f[i/2]+'0'
若 是奇数,那么 f[i]=f[i/2]+'1'
二进制翻转代码:
int bit=log_2(n)-1;
for(rint i=0;i<n;i++){
f[i]=f[i>>1]>>1|(i&1)<<bit;
}
这样,我们就可以把原序列中的每个数,先放在最终的位置上,再一步一步向上合并。就可以解决多项式乘法了P3803
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
#define complex New_complex
using namespace std;
const int N = 4e6 + 5;
const double pi = 2 * acos(-1);
struct New_complex{
double a,b;
complex(double x=0,double y=0){a=x,b=y;}
complex operator+(const complex&t)const{return complex(a+t.a,b+t.b);}
complex operator-(const complex&t)const{return complex(a-t.a,b-t.b);}
complex operator*(const complex&t)const{return complex(a*t.a-b*t.b,a*t.b+b*t.a);}
};
int n,m,k,r[N]; // 存变换之后的下标,即上面的 f[i]
complex a[N],b[N];
int log_2(int x){
int res=0;
if(x&0xffff0000){
res+=16;
x>>=16;
}
if(x&0xff00){
res+=8;
x>>=8;
}
if(x&0xf0){
res+=4;
x>>=4;
}
if(x&0xc){
res+=2;
x>>=2;
}
if(x&2){
res++;
}
return res;
}
void swap(complex &a,complex &b){
static complex t;
t=a,a=b,b=t;
return ;
}
void FFT(complex a[],int f){
for(rint i=0;i<k;i++)
if(i<r[i])
swap(a[i],a[r[i]]);// 将 a 变换成分治后的序列
complex t,w,x,y;// 要用到的四个变量,t 存单位根,w 存单位根的 i 次方,x 和 y 做临时变量
for(rint len=2;len<=k;len<<=1){ // 枚举区间长度
t=complex(cos(pi/len),f*sin(pi/len)); // 找到单位根
for(rint l=0;l<k;l+=len){ // 枚举所有区间左端点
w=complex(1,0); // 单位根的 0 次方为 1
for(rint i=0,j=len>>1;j<len;i++,j++,w=w*t){ // 分治处理
x=a[l+i];
y=w*a[l+j];
a[l+i]=x+y;
a[l+j]=x-y; // 先用 x 和 y 表示 a[l+i] 和 w*a[l+j],然后再计算,不然会覆盖掉前面的
}
}
}
}
signed main(){
cin>>n>>m;
for(rint i=0;i<=n;i++)
cin>>a[i].a;
for(rint i=0;i<=m;i++)
cin>>b[i].a;
int bit=log_2(n+m);
k=1<<bit+1; //这里用 k 存补后的多项式的次数
for(rint i=0;i<k;i++)
r[i]=r[i>>1]>>1|(i&1)<<bit; // 先将 r 数组处理好
FFT(a,1),FFT(b,1);
for(rint i=0;i<k;i++)
a[i]=a[i]*b[i];
FFT(a,-1);
for(rint i=0;i<=n+m;i++)
printf("%d ",(int)(a[i].a/k+0.5));
return 0;
}
8.三两优化
PS:这个优化名字是瞎叫的,它没有学名。。。。
设
那么
那么我们就可以把两个多项式存到一个数组里面,然后做两次 FFT
另外,注意到上面重复调用了很多次三角函数,我们可以直接预处理出
,可以减少很多调用三角函数的次数
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
#define complex New_complex
using namespace std;
const int N = 4e6 + 5;
const double pi = 2 * acos(-1);
struct New_complex{
double a,b;
complex(double x=0,double y=0){a=x,b=y;}
complex operator+(const complex&t)const{return complex(a+t.a,b+t.b);}
complex operator-(const complex&t)const{return complex(a-t.a,b-t.b);}
complex operator*(const complex&t)const{return complex(a*t.a-b*t.b,a*t.b+b*t.a);}
};
int n,m,k,r[N];
complex a[N],w[N];// w 预处理 omega^k , a 存两个多项式
int log_2(int x){
int res=0;
if(x&0xffff0000){
res+=16;
x>>=16;
}
if(x&0xff00){
res+=8;
x>>=8;
}
if(x&0xf0){
res+=4;
x>>=4;
}
if(x&0xc){
res+=2;
x>>=2;
}
if(x&2){
res++;
}
return res;
}
void swap(complex &a,complex &b){
static complex t;
t=a,a=b,b=t;
return ;
}
void FFT(bool type){
for(rint i=0;i<k;i++)
if(i<r[i])
swap(a[i],a[r[i]]);
static complex x,y;
for(rint len=2;len<=k;len<<=1){
for(rint l=0;l<k;l+=len){
for(rint i=0,j=len>>1;j<len;i++,j++){
x=a[l+i];
y=w[k/len*i];
if(type)
y=y*a[l+j];
else
y=complex(y.a,-y.b)*a[l+j];
a[l+i]=x+y;
a[l+j]=x-y;
}
}
}
return ;
}
int main(){
cin>>n>>m;
for(rint i=0;i<=n;i++)
cin>>a[i].a;
for(rint i=0;i<=m;i++)
cin>>a[i].b;
int bit=log_2(n+m);
k=1<<bit+1;
for(rint i=0;i<k;i++)
r[i]=r[i>>1]>>1|(i&1)<<bit;
w[0].a=1;
complex t(cos(pi/k),sin(pi/k));
for(rint i=1;i<k;i++)
w[i]=w[i-1]*t;
FFT(true);
for(rint i=0;i<k;i++)
a[i]=a[i]*a[i];
FFT(false);
for(rint i=0;i<=n+m;i++)
printf("%d ",(int)(a[i].b/k/2.0+0.5));
return 0;
}
好了,就这么多,FFT 仍需多加练习,才能在考场上灵活应用
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/16521234.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步