FFT (快速傅里叶变换)
本篇只说一下快速傅里叶变换的结构,说一下流程,具体细节及一些定理可以看
https://blog.csdn.net/giftedpanda/article/details/99414039
快速傅里叶变换就是用一系列的骚操作让多项式乘法变得更高效
有啥卵用呢?
比如两个数相乘,太大了,直接乘要出事情,与是乎我们可以把他看成两个多项式相乘,假装它带着x次方。想一想多项式相乘之后的系数不就是两个多项式系数相乘的相乘后的结果嘛!
比如 11 和 1相乘 结果为11
令a(x)=1+x (系数是不是为11)
b(x)=0+x =x (系数是不是为01)
c(x)=a(x)b(x) =x+x2 =0+x+x^2(系数是不是011) 把前缀0删了就行
先来引入一些概念
系数表达式
多项式的系数表达式就是最普通的表达式
比如上面的a(x)
专业一点的表达就是
点值表达式
多个点值确定一个多项式
{(x0,y0),(x1,y1),(x2,y2)……(xn-1,yn-1)}
跟函数的多个点确定一个函数差不多
单位复数根
比如一个复数z,一个复数w,z=wn 那么w就是z的n次方根
单位复数根就是复数1的n次方根
然后他的n次方根刚好有n个 ,这n个单位复数根均匀的分布在一个圆上
然后我们令一个主次方根
其余的根都是主次方根的幂次
那他的骚操作有些啥
1.DFT(离散傅里叶变换)
将多项式的系数表达式变换为点值表达式
用啥转,就用n个n次方单位复数根转
就会得到一个点值表达式
我们用n次方根对两个多项式求点值表达式 (两个表达式的n不再是他最初的n而是两个2n)
2. 傅里叶逆变换
将两个多项的点值相乘,得到一个新的点值,再根据新的点值进行傅里叶逆变换求出系数系数
可以观察出逆变换出来的系数就是用n次方单位复数根的共轭根进行个傅里叶变换,最后再除个n
3.快速傅里叶变换
两个多项式相乘结构就是先傅里叶变换,再进行傅里叶逆变换
用普通的傅里叶变换太慢了,所以给他优化一下就整出快速傅里叶变换
总体结构就变成了先快速傅里叶逆变换再快速傅里叶逆变换
采用分治策略,采用A(x)中偶数下标的系数和奇数下标的系数,分别定义两个新的次数界为n/2的多项式A[0] (x)和A[1](x)
就这么一直分治下去,分治到每个只有一个的时候,再往回倒着走传值,一直传到顶就是答案
每次对上一次分治的模块再进行奇,偶下标的那个操作。
然后最后就得到一个新的序列
神奇之处他最后的那个序列你可以直接得出,用二进制的逆序就可以
比如4的二进制是100(为啥只有三位,因为他只有8个元素,我们只在这8个元素里进行操作,而最高下标为7 ,7的二进制是111,所以只用三位)
4的二进制逆序是001 化为十进制就是1,那的分治到底的位置就是1
然后往回倒的时候,返回值不止返回一个,可以一次性返回两个,这是不是更高效了。
正常只返回
但是另一个点值
有没有发现他们有共同点,就是中间的那个符号不一样。
那我可以返回的时候一次整两个,这个操作是不是很骚
这就是大体结构,然后再配合食用细节及原理就差不多学会了
https://blog.csdn.net/giftedpanda/article/details/99414039
来个例题,也就是模板
问题 A: A * B
题目描述
Calculate A * B.
输入
Each line will contain two integers A and B. Process to end of file.
Note: the length of each integer will not exceed 50000.
输出
For each case output A * B in one line.
样例输入
1
2
1000
2
样例输出
2
2000
#include<bits/stdc++.h>
using namespace std;
typedef complex<double> cp;//复数 c++的复数
const double pi=acos(-1);
const int maxn=200000+5;
string sa,sb; //输入的系数
int n,lena,lenb,res[maxn]; //n为两个相乘后的最高次界 res存的是值
cp a[maxn],b[maxn],omg[maxn],inv[maxn];//a为sa的系数的实部 b为sb的系数的实部 omg为单位根,inv为单位根的共轭
void init(){
memset(omg,0,sizeof(omg));
memset(inv,0,sizeof(inv));
memset(res,0,sizeof(res));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
for(int i=0;i<n;i++){
omg[i]=cp(cos(2*pi*i/n),sin(2*pi*i/n));
inv[i]=conj(omg[i]);
}
}
void FFT(cp *a,cp*omg){ //快速傅里叶变换 用单位根去制造点值
int lim=0; //二进制最高次位
while((1<<lim)<n) //(2^lim)<n
lim++;
for(int i=0;i<n;i++){ //分治到最底层的位置
int t=0;
for(int j=0;j<lim;j++)
if((i>>j)&1) //二进制的右移操作,然后进行找1的操作
t|=(1<<(lim-j-1)); //t再和 2^(lim-j-1)与一下 就能交换的二进制里1的逆序
if(i<t) swap(a[i],a[t]); //只交换一次
}
for(int l=2;l<=n;l*=2){ //分治
int m=l/2;
for(cp *p=a;p!=a+n;p+=l){
for(int i=0;i<m;i++){
cp t=omg[n/l*i]*p[i+m]; //蝴蝶操作
p[i+m]=p[i]-t;
p[i]+=t;
}
}
}
}
int main(){
while(cin>>sa>>sb){
lena=sa.length();
lenb=sb.length();
n=1;
while(n<lena+lenb)
n*=2; //n为偶数 因为要用到折半定理
init(); //得到n后去初始化n次单位根
for(int i=0;i<lena;i++)
a[i].real(sa[lena-1-i]-'0'); //得到一个逆序 因为结果要消除前缀0
for(int i=0;i<lenb;i++)
b[i].real(sb[lenb-1-i]-'0');
FFT(a,omg); //用n次单位根去转换成点值
FFT(b,omg);
for(int i=0;i<n;i++)
a[i]*=b[i]; //点乘 结果保留在a数组
FFT(a,inv); //对结果进行傅里叶逆变换
for(int i=0;i<n;i++){
res[i]+=floor(a[i].real()/n+0.5); //傅里叶逆变换 然后向下取整
res[i+1]+=res[i]/10; //进位 模拟加法
res[i]%=10;
}
int len=lena+lenb-1;
while(res[len]<=0&&len>0) //消除前缀
len--;
for(int i=len;i>=0;i--)
printf("%c",'0'+res[i]);
printf("\n");
}
}