传送门:http://www.wikioi.com/problem/3123/
FFT,快速傅里叶变换,蒟蒻看别人的题解都太深奥,看不懂,好不容易学会,以蒟蒻的理解写给那些想学FFT却又找不到合适的资料的OIer,蒟蒻理解有限,难免有许多错误,请大家多多包涵。
快速傅里叶变换
百度的各种讲解都TM扯什么频率什么的,蒟蒻完全看不懂,后来认真看了看算导,获益匪浅,算导上讲的真心不赖,有很多内容都来自算导。
1.多项式
多项式的两种表达方式:系数表达和点值表达
系数表达就是大家常用的表达方式,点值表达就像在这个多项式函数上取n个不同的点,这样就可以确定原多项式。
比如说二次函数需要3个点就可以确定,一次函数需要2个点,一个n次多项式需要n个点(n次多项式意思是有0..n-1次幂的多项式)
A(x)=x^2+2*x-1可以被表达为{ ( 0 , -1 ) , ( 1 , 2 ) , ( 2 , 7 ) }
加法和乘法:
B(x)=x^2-x+2 { ( 0 , 2 ) , ( 1 , 2 ) , ( 2 , 4 ) }
C(x)=A(x)+B(x)=2x^2+x+1 { ( 0, 1) , ( 1 , 4 ) , ( 2, 11 ) }
注意乘法需要2n个点 lz比较懒就不写了……
于是我们得到一个计算多项式的方法:
2.n次单位复数根
有关复数根的性质可以百度到,不再赘述
http://baike.baidu.com/link?url=017EPfseoBwVxWpWPm5aunUn8x9dmRvioav9IubYLSKEGngK8_rDV2bd4PFCM8sJ
3.DFT&&FFT
使用单位根计算点值表达式叫DFT(离散傅里叶变换)复杂度n^2,FFT是其优化版复杂度nlogn
计算FFT的伪代码(好吧用的是python的高亮)
下划线代表的是下标,括号代表上标,for 循环的range是左闭右开的
FFT(a):
n=a.length()
if n==1:
return a
w_n=e^(pi*i/n)=complex(cos(2*pi/n),sin(2*pi/n))
w=1
a(0)=[a0,a2,...a_n-2]
a(1)=[a1,a3,...a_n-1]
y(0)=FFT(a(0))
y(1)=FFT(a(1))
for k in range(0,n/2):
y_k=y_k(0)+w*y_k(1)
y_k+n/2=y_k(0)-w*y_k(1)
w=w*w_n
return y
FFT的for循环中有两次w_n^k*y_k(1)的计算,于是可以改写成这样
for k in range(0,n/2):
t=w*y_k(1)
y_k=y_k(0)+t
y_k+n/2=y_k(0)-t
w=w*w_n
#这一过程被称蝴蝶操作
观察每次按照奇偶位置分割所形成的树:
每个数和他二进制相反的位置互换!!
伪代码(算导给的真是……)
BIT-REVERSE-COPY(a,A):
n=a.length()
for k in range(0,n):
A[rev(k)]=a_k
#算导说rev函数很好写,就没写……
FFT(a):
BIT-REVERSE-COPY(a,A)
n=a.length()
for s in range(1,log2(n)+1):
m=2^s
w_m=e^(2*pi*i/m)=complex(cos(2*pi*m),sin(2*pi*m))
for k in range(0,n,m):
w=1
for j in range(0,m/2):
t=w*A[k+j+m/2]
u=A[k+j]
A[k+j]=u+t
A[k+j+m/2]=u-t
w=w*w_m
return A
差不多讲完了,最后给出C++代码,有一大部分是lz借鉴别人的Code,以后附上地址
#include
#include
#include
#include
#include
#define N 400005
#define pi acos(-1.0) // PI值
using namespace std;
struct complex
{
double r,i;
complex(double real=0.0,double image=0.0){
r=real; i=image;
}
// 以下为三种虚数运算的定义
complex operator + (const complex o){
return complex(r+o.r,i+o.i);
}
complex operator - (const complex o){
return complex(r-o.r,i-o.i);
}
complex operator * (const complex o){
return complex(r*o.r-i*o.i,r*o.i+i*o.r);
}
}x1[N],x2[N];
char a[N/2],b[N/2];
int sum[N]; // 结果存在sum里
int vis[N];
void brc(complex *a,int l){//原来神犇的二进制平摊反转置换太神看不懂,蒟蒻写了一个O(n)的……
memset(vis,0,sizeof(vis));//O(logn)的在后面
for(int i=1;i>=1;
}
vis[i]=vis[y]=1;
swap(a[i],a[y]);
}
}
void fft(complex *y,int l,double on) // FFT O(nlogn)
// 其中on==1时为DFT,on==-1为IDFT
{
register int h,i,j,k;
complex u,t;
brc(y,l); // 调用反转置换
for(h=2;h<=l;h<<=1) // 控制层数
{
// 初始化单位复根
complex wn(cos(on*2*pi/h),sin(on*2*pi/h));
for(j=0;j>t)==1)return;//s记录起始,t记录深度,调用时应从0开始
fft(a,s,t+1);
fft(a,s+(1<>(t+1));i++){
p=(i<<(t+1))+s;
wt=w[i<>(t+1))]=a[p]-wt;
}
for(i=0;i<(n>>t);i++)a[(i<0) l--; // 检索最高位
for(i=l;i>=0;i--) putchar(sum[i]+'0'); // 倒序输出
putchar('\n');
}
return 0;
}
/*void brc(complex *y,int l) // 二进制平摊反转置换 O(logn)
{
register int i,j,k;
for(i=1,j=l/2;i=k) // 由最高位检索,遇1变0,遇0变1,跳出
{
j-=k;
k>>=1;
}
if(j
pyc神犇的写法,bzoj3527,zjoi2014 力,无限YM
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1000010;
int n,N,L;
int rev[maxn];
int dig[maxn];
double p[maxn];
struct cp{
double r,i;
cp(double _r=0,double _i=0):
r(_r),i(_i){}
cp operator+(cp x){return cp(r+x.r,i+x.i);}
cp operator-(cp x){return cp(r-x.r,i-x.i);}
cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}
};
cp a[maxn],b[maxn],c[maxn],A[maxn],x,y;
void FFT(cp a[],int flag){
for(int i=0;i>=1)dig[len++]=t&1;
for(int j=0;j
重新过了一遍高精乘
#include
#include
#include
#include
#include
using namespace std;
const int maxn=1e6+10;
struct cp{
double r,i;
cp(double _r=0,double _i=0):
r(_r),i(_i){}
cp operator+(cp x){return cp(r+x.r,i+x.i);}
cp operator-(cp x){return cp(r-x.r,i-x.i);}
cp operator*(cp x){return cp(r*x.r-i*x.i,r*x.i+i*x.r);}
};
cp a[maxn],b[maxn],A[maxn],x,y,c[maxn];
char s1[maxn],s2[maxn];
int sum[maxn],a1[maxn],a2[maxn],dig[maxn];
int len1,len2,rev[maxn],N,L;
void FFT(cp a[],int flag){
for(int i=0;i>=1)dig[len++]=t&1;
for(int j=0;j0)l--;
for(int i=l;i>=0;i--)
putchar(sum[i]+'0');
putchar('\n');
return 0;
}