FFT

前置知识

多项式的表示:一般来说,多项式的表示方法有点表示和系数表示两种。

系数表示法:一个\(n\)次多项式可以由\(n+1\)个系数唯一确定,也即\(f(x)=\sum_{i=0}^{n}a_ix^{i}\)

点表示法:一个\(n\)次多项式可以由\(n+1\)个各不相同的点唯一确定,设这\(n+1\)个点的坐标为\((x_0,f(x_0)),(x_1,f(x_1)),\cdots,(x_n,f(x_n))\),代入表达式可得方程组: \[ \begin{cases} a_0+a_1x_0+\cdots+a_nx_0^{n}=f(x_0)\\ a_0+a_1x_1+\cdots+a_nx_1^{n}=f(x_1)\\ \cdots\\ a_0+a_1x_n+\cdots+a_nx_n^{n}=f(x_n)\end{cases}\] 其系数行列式的值由范德蒙行列式可求得: \[\begin{vmatrix}1 & x_0 & \dots &x_0^{n}\\ 1 & x_1 & \dots & x_1^{n} \\ \vdots & \vdots & \ddots & \vdots \\ 1 & x_n & \dots & x_n^{n} \end{vmatrix} = \prod_{0\leq i\leq j \leq n}(x_i-x_j)\neq 0\] 可知多项式的系数被唯一确定。

复数的表示与运算:一个复数可以表示为\(z=a+bi(a,b\in \mathbb{R})\),对应复平面上一点\(P(a,b)\)。其模长\(|z|=\sqrt {a^{2}+b^{2}}\),当\(z\neq0\)时,定义向量\(\overrightarrow{OP}\)\(x\)轴正向夹角为辐角\(\theta\),则复数也可表示为\(z=|z|(\cos\theta+i\sin\theta)\)

复数的加(减)法:设\(z_1=a_1+ib_1\)\(z_2=a_2+ib_2\),则\(z_1\pm z_2=(a_1\pm a_2)+i(b_1 \pm b_2)\)

复数的乘法:设\(z_1=a_1+ib_1\)\(z_2=a_2+ib_2\),则\(z_1z_2=(a_1a_2-b_1b_2)+i(a_1b_2+a_2b_1)\)。复数的乘法也可以理解为模长相乘,辐角相加。

单位根:考虑\(x^{n}=1\)在复数域上的\(n\)个解,在单位圆上将圆从\((1,0)\)开始等分成n份,这\(n\)个点均满足上述方程,将它们对应的复数记为\(\omega_n^{i}(1 \leq i \leq n-1)\),且有\(\omega_n^{i}=\cos{\frac{2\pi i}{n}}+i\sin{\frac{2\pi i}{n}}\)

单位根的性质:1)\(\omega_{2n}^{2k}=\omega_n^{k}\)

2)\(\omega_n^{k+\frac{n}{2}}=-\omega_n^{k}\)

3)\(\omega_n^{0}=\omega_n^{n}=1\)

FFT(快速傅里叶变换)

FFT是用来解决两个多项式相乘的一种算法,FFT大致思路如下:先将两个多项式写成点表示法,则所求多项式的点值等于两个多项式点值的乘积,再将其重新转化为系数表示法。

将系数表示法转化为点表示法

\(n\)为2的次幂,设\(A(x)=a_0+a_1x+\cdots+a_{n-1}x^{n-1}\),将下标按奇偶性分开,则

\(A(x)=(a_0+a_2x^{2}+\cdots+a_{n-2}x^{n-2})+(a_1x+a_3x^{3}+\dots+a_{n-1}x^{n-1})\)

\(=(a_0+a_2x^{2}+\cdots+a_{n-2}x^{n-2})+x(a_1+a_3x^{2}+\cdots+a_{n-1}x^{n-2})\)

\(A_1(x)=a_0+a_2x+\cdots+a_{n-2}x^{\frac{n}{2}-1}\)\(A_2(x)=a_1+a_3x+\cdots+a_{n-1}x^{\frac{n}{2}-1}\)

\(A(x)=A_1(x^{2})+xA_2(x^{2})\),将\(\omega_n^{k}(k<\frac{n}{2})\)代入得:

\(A(\omega_n^{k})=A_1(\omega_n^{2k})+xA_2(\omega_n^{2k})=A_1(\omega_\frac{n}{2}^{k})+\omega_n^{k}A_2(\omega_\frac{n}{2}^{k})\)

\(A(\omega_n^{k+\frac{n}{2}})=A_1(\omega_n^{2k+n})+xA_2(\omega_n^{2k+n})=A_1(\omega_\frac{n}{2}^{k})-\omega_n^{k}A_2(\omega_\frac{n}{2}^{k})\)

可以发现两部分只有后半部分的符号不一样,则若已知\(A_1(\omega_\frac{n}{2}^{k})\)\(A_2(\omega_\frac{n}{2}^{k})\)可以求得\(A(\omega_n^{k})\)\(A(\omega_n^{k+\frac{n}{2}})\),从而我们可以使用递归分治的方式求得\(A(\omega_n^{k})(0\leq k\leq n-1)\),时间复杂度\(O(nlogn)\)

将点表示法重新转化为系数表示法

已知\(n+1\)个点的坐标\((x_0,A(x_0)),(x_1,A(x_1)),\cdots,(x_{n-1},A(x_{n-1}))\),其中\(x_i=\omega_n^{i}(0\leq i\leq n-1)\),先证明:\[ a_k=\frac{1}{n}\sum_{i=0}^{n-1}A(\omega_n^{i})(\omega_n^{-k})^i\]证明:

\[\sum_{i=0}^{n-1}A(\omega_n^{i})(\omega_n^{-k})^i\] \[=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^i)^j)(\omega_n^{-k})^i\] \[=\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^{j-k})^i)\] \[=\sum_{j=0}^{n-1}a_j(\sum_{i=0}^{n-1}(\omega_n^{j-k})^i)\tag{1}\]

\(S(x)=1+x+x^2+\cdots+x^{n-1}\),考虑\(S(\omega_n^{k})\)

1)若\(k=0\)\(S(\omega_n^{k})=n\)

2)若\(k\neq 0\)\(S(\omega_n^{k})=\omega_n^0+\omega_n^k+\cdots+\omega_n^{(n-1)k}\)\(\omega_n^{k}S(\omega_n^{k})=\omega_n^k+\omega_n^{2k}+\cdots+\omega_n^{nk}\),两式相减可得:\((1-\omega_n^k)S(\omega_n^k)=0\),则\(S(\omega_n^{k})=0\)

从而\((1)=na_k\),问题得证。

\(B(x)=\sum_{i=0}^{n-1}A(\omega_n^{i})x^i\),则\(a_k=\frac{1}{n}B(\omega_n^{-k})\),问题又转化为了从系数表示法转化为点表示法。

迭代优化

注意到最终的位置为其初始位置二进制翻转后得到的,则可以将最终的位置处理出来后,向上迭代处理就可以得到最初的序列。

代码

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
#include<iostream>
#include<cmath>
using namespace std;
const double pi = acos(-1);
struct Complex
{
double x,y;
Complex operator+ (const Complex& t)const
{
return {x + t.x,y + t.y};
}
Complex operator- (const Complex& t)const
{
return {x - t.x,y - t.y};
}
Complex operator* (const Complex& t)const
{
return {x * t.x - y * t.y,x * t.y + y * t.x};
}
}a[300010],b[300010];
int n,m,bit,tot,pos[300010];
void fft(Complex a[],int mode)
{
for(int i = 0;i < tot;i++)
{
if(i < pos[i])
swap(a[i],a[pos[i]]);
}
for(int mid = 1;mid < tot;mid = mid * 2)
{
auto wi = Complex({cos(pi / mid),mode * sin(pi / mid)});
for(int i = 0;i < tot;i = i + mid * 2)
{
auto w0 = Complex({1,0});
for(int j = 0;j < mid;j++)
{
auto tx = a[i + j],ty = w0 * a[i + j + mid];
a[i + j] = tx + ty,a[i + j + mid] = tx - ty;
w0 = w0 * wi;
}
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i = 0;i <= n;i++)
scanf("%lf",&a[i].x);
for(int i = 0;i <= m;i++)
scanf("%lf",&b[i].x);
while((1 << bit) < n + m + 1)
bit++;
tot = 1 << bit;
for(int i = 0;i < tot;i++)
pos[i] = (pos[i >> 1] >> 1) | ((i & 1) << (bit - 1));
fft(a,1),fft(b,1);
for(int i = 0;i < tot;i++)
a[i] = a[i] * b[i];
fft(a,-1);
for(int i = 0;i <= n + m;i++)
printf("%d ",(int)(a[i].x / tot + 0.5));
system("pause");
return 0;
}