快速傅里叶变换

初探多项式

快速傅里叶变换

前置知识

复数

1.前言

快速傅里叶变换是用来加速多项式乘法的,对于两个多项式 AABB ,朴素计算时的n2n^2是满足不了需求的,快速傅里叶变换可以在O(logn)O(\log n)的时间内计算A×BA \times B

先看一下单位元的几个性质,在接下来的算法中有很大的用途。

ωnk=e2πikn(1,1)\tag{1,1} \omega_n^k = e ^{\frac{2 \pi i k}{n}}

ωdndk=ωnk(1,2)\tag{1,2} \omega_{dn}^{dk} = \omega_n^k

ωnk=a+biωnk=abi(1,3)\tag{1,3} \omega_{n}^k = a+bi \\ \omega_{n}^{-k} = a-bi

ωnk+n2=ωnk(1,4)\tag{1,4} \omega_n^{k+ \frac{n}{2}} = - \omega_n^k

以上变换均可由欧拉公式 eiθ=cosθ+isinθe^{i \theta}= \cos\theta + i \cdot \sin\theta 推得。

2.离散傅里叶变换

离散傅里叶变换(DFT) 主要是利用分治思想,

首先将多项式

A(x)=i=0naixi(2,1)\tag{2,1} A(x) = \sum_{i=0} ^n a_i x^i

其系数进行奇偶性分类,得到,

A0(x)=a0+a2x1+a4x2+A1(x)=a1+a3x1+a5x2+(2,2)\tag{2,2} A_0(x)= a_0+a_2 x^1 +a_4 x^2 + \cdots \\ A_1(x)= a_1+a_3 x^1 +a_5 x^2 + \cdots \\

所以我们可以表示为 :

A(x)=A0(x2)+xA1(x2)(2,3)\tag{2,3} A(x) = A_0 (x^2) +x \cdot A_1(x^2)

ωnk\omega_n^kωnk+n2\omega_n^{k+ \frac{n}{2}}代入得:

{A(ωnk)=A(ωn2k)+ωnkA1(ωn2k)A(ωnk+n2)=A(ωn2k)ωnkA1(ωn2k)(2,4)\tag{2,4} \left\{ \begin{aligned} &A(\omega_n^k) &= &A(\omega_n^{2k})+\omega_n^k A_1(\omega_n^{2k}) \\ &A(\omega_n^{k+ \frac{n}{2}}) &= &A(\omega_n^{2k})-\omega_n^k A_1(\omega_n^{2k}) \\ \end{aligned} \right.

同时我们可以发现两个式子只有常数一样,递归计算即可。

时间复杂度O(nlogn)O(n \log n)

在这里我们将系数变成了点值

3. 离散傅里叶逆变换

离散傅里叶逆变换IDFT 可以将点值快速转化为系数,从而得出结果多项式。

需要用到单位根的一个性质:

1ni=0n1ωnxi=[xmodn=0]\frac{1}{n} \sum_{i=0}^{n-1} \omega_n^{x \ast i} = [x \bmod n =0]

证明 :
由于 ωnxi=ωnx(i1)ωnx\omega_n ^ {x \ast i} = \omega_n^ {x \ast (i-1)} \ast \omega_n^x

所以ωnxi\omega _n ^{x\ast i} 为等比数列

1ni=0n1ωnxi={1ni=0n11i=nn=1xmodn=01n1ωnnx1ωnx=1n11x1ωnx=0xmodn0(3,1)\tag{3,1} \therefore \frac{1}{n} \sum_{i=0}^{n-1} \omega_n^{x \ast i}= \left\{ \begin{aligned} &\frac{1}{n} \sum_{i=0}^{n-1} 1^i = \frac{n}{n} = 1 & x \bmod n=0\\ &\frac{1}{n} \cdot \frac{1- \omega _n ^ {n \ast x}}{1-\omega _n ^ x} = \frac{1}{n} \cdot \frac{1-1^x}{1-\omega_n^x} =0 & x\bmod n \ne 0 \end{aligned} \right.

证明:

c=abci=j=0iajbij=p=0q=0apbq[(p+q)modn=0]nci=p=0q=0apbqj=0ωn(p+qi)j=j=0ωn(i)j(p=0ωnpjap)(q=0ωnqjbq)fa(j)=i=0ωnijai,fa1(j)=i=0ωn(i)jainci=j=0ωn(i)jfa(j)fb(j)=j=0ωn(i)jfc(j)=ffc1(i)(3,2)\tag{3,2} 设 c= a\ast b \\ \begin{aligned} c_i &= \sum_{j=0}^i a_j \cdot b_{i-j} \\ &=\sum_{p=0}\sum_{q=0} a_p \cdot b_q [(p+q) \bmod n=0] \\ nc_i &= \sum_{p=0}\sum_{q=0} a_p \cdot b_q \sum_{j=0} \omega_n^{(p+q-i)j}\\ &= \sum_{j=0}\omega_n^{(-i)j} \bigg( \sum_{p=0} \omega_n^{pj} a_p\bigg) \bigg( \sum_{q=0} \omega_n^{qj} b_q\bigg) \end{aligned} \\ 设 f_a(j) = \sum_{i=0} \omega_n^{ij} a_i , f_a^{-1}(j) =\sum_{i=0} \omega_n^{(-i)j} a_i \\ \begin{aligned} nc_i &= \sum_{j=0} \omega_n^{(-i)j}f_a(j)f_b(j) \\ &= \sum_{j=0} \omega_n^{(-i)j}f_c(j) \\ &= f_{f_c}^{-1} (i) \end{aligned}

因为 faf_a 就是 aa 在 DFT 后的结果,所以fa1f_a^{-1}就是 对应的IDFT,最后除以对应长度nn,即为所求。

4.代码实现

在写FFT 时可以将DFT和IDFT合在一起这样会更简便,同时使用位逆序变换,更加简便快捷。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e7+10;
const double pi=acos(-1.0);
struct Complex{
double a,b;
Complex(double x=0,double y=0):a(x),b(y) {}
friend Complex operator + (Complex x,Complex y){
return Complex(x.a+y.a,x.b+y.b);
}
friend Complex operator - (Complex x,Complex y){
return Complex(x.a-y.a,x.b-y.b);
}
friend Complex operator * (Complex x,Complex y){
return Complex(x.a*y.a-x.b*y.b,x.b*y.a+y.b*x.a);
}
};
int n,m,recover[maxn];
Complex F[maxn],G[maxn],H[maxn];
void FFT(Complex *a,int len,int type)
{
for(int i=0;i<len;i++)
if(i<recover[i])swap(a[i],a[recover[i]]);


for(int k=1;k<len;k<<=1)
{
Complex x(cos(pi/k),type*sin(pi/k));
for(int i=0;i<len;i+=(k<<1))
{
Complex w(1,0);
for(int j=0;j<k;j++)
{
Complex y=a[i+j];
Complex z=w*a[i+j+k];
a[i+j]=y+z;a[i+j+k]=y-z;
w=w*x;
}
}
}
if(type==-1)
for(int i=0;i<len;i++)a[i].a/=len;
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
{
double x;
scanf("%lf",&x);
F[i].a=x;
}
for(int i=0;i<=m;i++)
{
double x;
scanf("%lf",&x);
G[i].a=x;
}
int len=1,cnt=0;
while(len<=(n+m))len<<=1,cnt++;
for(int i=0;i<len;i++)
recover[i]=(recover[i>>1]>>1)|((i&1)<<(cnt-1));
FFT(F,len,1);FFT(G,len,1);
for(int i=0;i<=len;i++)H[i]=F[i]*G[i];
FFT(H,len,-1);
for(int i=0;i<=n+m;i++)
printf("%d ",(int)(H[i].a+0.5));
return 0;
}
作者

Jekyll_Y

发布于

2022-07-21

更新于

2023-03-02

许可协议

评论