我们知道,多项式可以表示成:
首先,我们得知道一个东西:多项式的点值表示法。
我们把上面的称为多项式的系数表示法,而点值表示法就是:
若 A A 多项式的次数为 n n ,则任取 n n 个不相同的 x0,x1,⋯,xn x 0 , x 1 , ⋯ , x n ,求出 A A 多项式的 A(x0),A(x1),⋯,A(xn) A ( x 0 ) , A ( x 1 ) , ⋯ , A ( x n ) 。记为:
对于一个点值表示法下多项式
等等,我们好像找到了一个突破口!
为啥不把原来的系数表示法下的多项式转化成点值表示法呢?
仔细想一想:系数表示法与点值表示法互相转换,这个步骤好像是 O(n2) O ( n 2 ) 的。
而FFT(快速傅里叶变换)就是为了优化这个 O(n2) O ( n 2 ) 。
PS:对于 O(n2) O ( n 2 ) 的点值表示法转化成系数表示法可以看百度百科中关于插值法的介绍。
如果未特别说明,那么下面的多项式次数将是 2k−1 2 k − 1 的形式。
如果不是关键部分的公式或定理,不提供证明,自己出门右转百度。
首先介绍两个概念:
DFT(离散傅里叶变换)是将多项式由系数表示法转化成点值表示法;
IDFT(离散傅里叶逆变换)是将多项式由点值表示法转化成系数表示法;
而FFT就是上述两种变换的优化。
前置技能:
下面的内容将会提到复数,不会的可以参考百度百科中关于复数的介绍;
为了介绍FFT中的DFT部分,首先要介绍的是一个概念:单位根。
单位根:若有
为了表示 n n 次单位根,我们引入一个公式。
欧拉公式:
如果我们令:
下面是关于 ωn ω n 的两条性质:(都是在 n n 为偶数的情况下)
下面,我们进入正题:DFT的求法。
在这里,我们令多项式次数为 n−1 n − 1 ,那么我们可以用点值表示成
额……这时间复杂度好像并没有减少……
别急,我们来看 A(ωkn) A ( ω n k ) 能够表示成什么。
我们来分别看一看这神奇的步骤。
(3) ( 3 ) 步骤就是将 ωkn ω n k 带入原来的 A A 多项式。
(4) ( 4 ) 步骤就是将原多项式拆成两个部分,按奇偶分类。
(5) ( 5 ) 步骤用到了上面提到的性质 (2) ( 2 ) 。
(6) ( 6 ) 步骤就是上面式子的后半部分提出公因数。
有了这个等式,我们就可以分治+递归解决DFT了。
算法步骤:
但是这个的常数好像很大啊?能不能减少一点呢?
上面的性质 (1) ( 1 ) 给了我们提示:
在算 k<n2 k < n 2 时,可以顺便把 k≥n2 k ≥ n 2 的情况也算出来。
常数减小了一半!但是还是很大啊!
递归版的程序一般比非递归版慢,为啥不用非递归版呢?
算法核心就是奇偶分类,分来分去最后分到了哪里?我们来研究研究。
显然,一个序列原来是 0,1,2,3,4,5,6,7 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,最终变成 0,4,2,6,1,5,3,7 0 , 4 , 2 , 6 , 1 , 5 , 3 , 7 。
把它们的二进制列出来:
其中,上面是位置,下面是这个位置对应的数。
把上面的数翻转,好像就是下面的数!
没错,只需要计算一下每个数的二进制翻转后的结果,就能得到一个数最终对应的位置,也就能实现非递归版了。
代码:
int dft_fast(complex* ar,int len)
{
for(register int i=0; iif(rev[i]std::swap(ar[rev[i]],ar[i]);//交换一个位置和它的翻转后位置
}
}
for(register int i=2; i<=len; i<<=1)//i代表当前序列的长度
{
complex wn(cos(2*pi/i),sin(2*pi/i));//omega_n
for(register int j=0; j//j代表序列的起始位置
{
complex w(1,0);//下面代表omega_n^k
for(register int k=0; k<(i>>1); ++k)//枚举i次单位根的每一种取值
{
complex x=ar[j+k],y=w*ar[j+k+(i>>1)];
ar[j+k]=x+y;//合并操作,将两边合并成一个点值表示法下的多项式
ar[j+k+(i>>1)]=x-y;
w=w*wn;
}
}
}
return 0;
}
回顾上面的DFT部分,仔细思考一下,它本质就是在求:
其中,给定了 a0,a1,⋯,an−1 a 0 , a 1 , ⋯ , a n − 1 以及 ω0n,ω1n,⋯,ωn−1n ω n 0 , ω n 1 , ⋯ , ω n n − 1 ,求 A(ω0n),A(ω1n),⋯,A(ωn−1n) A ( ω n 0 ) , A ( ω n 1 ) , ⋯ , A ( ω n n − 1 ) 的值。
用矩阵表示如下:
我们令:
那么IDFT的本质就是求 V V 矩阵的逆矩阵。
考虑下面这个矩阵:
那么我们令 E=D⋅V E = D ⋅ V ,则:
显然:
因此,
这就相当于把DFT中 ωkn ω n k 都换成 ω−kn ω n − k 。
int fft(complex* ar,int len,int op)
{
for(register int i=0; iif(rev[i]std::swap(ar[rev[i]],ar[i]);
}
}
for(register int i=2; i<=len; i<<=1)
{
complex wn(cos(2*pi/i),sin(2*pi*op/i));//只有这里较DFT代码有变动
for(register int j=0; jcomplex w(1,0);
for(register int k=0; k<(i>>1); ++k)
{
complex x=ar[j+k],y=w*ar[j+k+(i>>1)];
ar[j+k]=x+y;
ar[j+k+(i>>1)]=x-y;
w=w*wn;
}
}
}
if(op==-1)
{
for(register int i=0; ireturn 0;
}
#include
#include
#include
const int maxn=100000;
const double pi=acos(-1);
struct complex
{
double r,i;
complex(double r_=0,double i_=0)
{
r=r_;
i=i_;
}
complex operator +(const complex &other)
{
return complex(r+other.r,i+other.i);
}
complex operator -(const complex &other)
{
return complex(r-other.r,i-other.i);
}
complex operator *(const complex &other)
{
return complex(r*other.r-i*other.i,r*other.i+i*other.r);
}
};
complex a[maxn<<2],b[maxn<<2],c[maxn<<2];
int rev[maxn<<2],n,m;
int fft(complex* ar,int len,int op)
{
for(register int i=0; iif(rev[i]std::swap(ar[rev[i]],ar[i]);
}
}
for(register int i=2; i<=len; i<<=1)
{
complex wn(cos(2*pi/i),sin(2*pi*op/i));
for(register int j=0; jcomplex w(1,0);
for(register int k=0; k<(i>>1); ++k)
{
complex x=ar[j+k],y=w*ar[j+k+(i>>1)];
ar[j+k]=x+y;
ar[j+k+(i>>1)]=x-y;
w=w*wn;
}
}
}
if(op==-1)
{
for(register int i=0; ireturn 0;
}
int main()
{
scanf("%d%d",&n,&m);
for(register int i=0; i<=n; ++i)
{
scanf("%lf",&a[i].r);
}
for(register int i=0; i<=m; ++i)
{
scanf("%lf",&b[i].r);
}
n=n+m;
int l=0;
m=1;
while(m<=n)
{
++l;
m<<=1;
}
for(register int i=0; i>1]>>1)|((i&1)<<(l-1));
}
fft(a,m,1);
fft(b,m,1);
for(register int i=0; i1);
for(register int i=0; iprintf("%d ",(int)(c[i].r+0.5));
}
printf("%d\n",(int)(c[n].r+0.5));
return 0;
}