前置知识
多项式
一个多项式表示为 A ( x ) = a 0 + a 1 x + a 2 x 2 + . . . a n x n A(x) = a_0 + a_1 x + a_2 x^2 + ... a_n x^n A(x)=a0+a1x+a2x2+...anxn
系数表示法: A ( x ) = ∑ i = 0 n a i x i A(x) = \sum _{i=0}^{n} a_i x^i A(x)=∑i=0naixi
点值表示法:用 n n n 个不同的点 ( x , A ( x ) ) (x, A(x)) (x,A(x)) 唯一表示多项式 A ( x ) A(x) A(x)
复数
前置知识:向量、三角函数
定义
设 i 2 = − 1 i^2 = -1 i2=−1 ,则一个复数表示为 a + b i ( a , b ∈ R ) a + bi (a,b\in R) a+bi(a,b∈R)
其中 b i bi bi 为虚部, a a a 为实部
复平面上,从 ( 0 , 0 ) (0,0) (0,0) 到 ( a , b ) (a,b) (a,b) 的向量表示 a + b i a+bi a+bi
棱长: ∣ Z ∣ = a 2 + b 2 |Z| = \sqrt{a^2+b^2} ∣Z∣=a2+b2
幅角:为从 x x x 正半轴逆时针旋转的角度
运算
加法: ( a + b i ) + ( c + d i ) = ( a + c ) + ( b + d ) i (a+bi)+(c+di) = (a+c)+(b+d)i (a+bi)+(c+di)=(a+c)+(b+d)i
乘法几何意义:复数相乘,模长相乘,幅角相加
乘法代数意义: ( a + b i ) ( c + d i ) = ( a c − b d ) + ( a d + b c ) i (a+bi)(c+di) = (ac-bd)+(ad+bc)i (a+bi)(c+di)=(ac−bd)+(ad+bc)i
共轭复数
复数 Z = a + b i Z = a+bi Z=a+bi 的共轭复数 Z ˉ \bar{Z} Zˉ 为 a − b i a-bi a−bi
∣ Z ∣ = ∣ Z ˉ ∣ Z ⋅ Z ˉ = a 2 + b 2 |Z|=|\bar{Z}|\ \ \ \ \ \ \ \ Z\cdot \bar{Z} = a^2+b^2 ∣Z∣=∣Zˉ∣ Z⋅Zˉ=a2+b2
复数除法
z 1 = a 1 + b 1 i , z 2 = a 2 + b 2 i z_1=a_1+b_1i,z_2=a_2+b_2i z1=a1+b1i,z2=a2+b2i
设 z 0 = z 1 z 2 z_0=\frac{z_1}{z_2} z0=z2z1
上下同乘 z 2 z_2 z2 的共轭复数 z 2 ˉ \bar{z_2} z2ˉ
z 1 z 2 ˉ a 2 2 + b 2 2 \frac{z_1 \bar{z_2}}{a_2^2 + b_2^2} a22+b22z1z2ˉ
单位根 ω
为在复平面上以原点为圆心作半径为 1 1 1 的圆。
ω n k \omega_n^k ωnk 表示为将圆分成 n n n 份,从 x x x 正半轴逆时针取 k k k 份下的复数。
-
ω n k = ω n k − 1 ⋅ ω n 1 \omega_n^k = \omega _{n}^{k-1} \cdot \omega _n^1 ωnk=ωnk−1⋅ωn1
-
ω n 0 = ω n n = 1 \omega_n^0 = \omega_n^n=1 ωn0=ωnn=1
-
ω 2 n 2 k = ω n k \omega_{2n}^{2k} = \omega_n^k ω2n2k=ωnk
-
ω n k + n 2 = − ω n k \omega_n^{k+\frac{n}{2}} = - \omega_n^k ωnk+2n=−ωnk
算法流程
快速傅里叶变换
快速傅里叶变换( Fast Fourier Transform , FFT \texttt{Fast Fourier Transform , FFT} Fast Fourier Transform , FFT)
使用 ω n k ( 0 ≤ k < n ) \omega_n^k (0\leq k < n) ωnk(0≤k<n) 这 n n n 个不同的数来当作点值表示法中的 n n n 个 x x x
已知
A ( x ) = ∑ i = 0 n − 1 a i ∗ x i A(x) = \sum_{i=0}^{n-1} a_i * x^i A(x)=i=0∑n−1ai∗xi
设
A 1 ( x ) = a 0 + a 2 ⋅ x + a 4 ⋅ x 2 + ⋯ + a n − 2 x n 2 − 1 A_1(x) = a_0 + a_2 \cdot x + a_4 \cdot x^2 + \cdots + a_{n-2} x^{\frac{n}{2}-1} A1(x)=a0+a2⋅x+a4⋅x2+⋯+an−2x2n−1
A 2 ( x ) = a 1 + a 3 ⋅ x + a 5 ⋅ x 2 + ⋯ + a n − 1 x n 2 − 1 A_2(x) = a_1 + a_3 \cdot x + a_5 \cdot x^2 + \cdots + a_{n-1} x^{\frac{n}{2}-1} A2(x)=a1+a3⋅x+a5⋅x2+⋯+an−1x2n−1
则
A ( x ) = A 1 ( x 2 ) + x A 2 ( x 2 ) A(x) = A_1(x^2) + xA_2(x^2) A(x)=A1(x2)+xA2(x2)
将 ω n k ( 0 ≤ k < n ) \omega_n^k (0\leq k < n) ωnk(0≤k<n) 代入 A ( x ) A(x) A(x) 中
A ( ω n k ) = A 1 ( ω n 2 k ) + ω n k A 2 ( ω n 2 k ) A(\omega_n^k) = A_1(\omega_{\frac{n}{2}}^{k}) + \omega_n^k A_2(\omega_{\frac{n}{2}}^{k}) A(ωnk)=A1(ω2nk)+ωnkA2(ω2nk)
A ( ω n n 2 + k ) = A 1 ( ω n 2 k ) − ω n k A 2 ( ω n 2 k ) A(\omega_n^{\frac{n}{2}+k}) = A_1(\omega_{\frac{n}{2}}^{k}) - \omega_n^k A_2(\omega_{\frac{n}{2}}^{k}) A(ωn2n+k)=A1(ω2nk)−ωnkA2(ω2nk)
两个式子 A 1 ( ) A_1() A1() , A 2 ( ) A_2() A2() 相同。
当计算 [ 0 , n 2 ) [0,\frac{n}{2}) [0,2n) 时, [ n 2 , n ) [\frac{n}{2},n) [2n,n) 可以直接计算。
每次计算量减小一半,时间复杂度 O ( n log n ) O(n\log n) O(nlogn)
快速傅里叶逆变换
快速傅里叶逆变换 ( Inverse Fast Fourier Transform , IFFT ) (\texttt{Inverse Fast Fourier Transform , IFFT}) (Inverse Fast Fourier Transform , IFFT)
设 C ( x ) = c 0 + c 1 x + c 2 x 2 + ⋯ + c n − 1 x n − 1 C(x) = c_0 + c_1 x + c_2 x^2 + \cdots + c_{n - 1} x^{n-1} C(x)=c0+c1x+c2x2+⋯+cn−1xn−1
设 b i = ω n i , y i = C ( b i ) b_i = \omega_n^i, y_i = C(b_i) bi=ωni,yi=C(bi)
C ( x ) C(x) C(x) 经过 n n n 个点, ( b 0 , y 0 ) … ( b n − 1 , y n − 1 ) (b_0, y_0) \dots (b_{n - 1}, y_{n - 1}) (b0,y0)…(bn−1,yn−1)
引理1
设
S ( x ) = 1 + x + x 2 + ⋯ + x n − 1 S(x) = 1 + x + x^2 + \cdots + x^{n-1} S(x)=1+x+x2+⋯+xn−1
因为
S ( ω n k ) = ω n 0 + ω n k + ω n 2 k + ⋯ + ω n ( n − 1 ) k S(\omega_n^k) = \omega_n^0 + \omega_n^k + \omega_n^{2k} + \cdots + \omega_n^{(n-1)k} S(ωnk)=ωn0+ωnk+ωn2k+⋯+ωn(n−1)k
又
ω n k S ( ω n k ) = ω n k + ω n 2 k + ⋯ + ω n ( n − 1 ) k + ω n n k \omega_n^k S(\omega_n^k) = \omega_n^k + \omega_n^{2k} + \cdots + \omega_n^{(n-1)k} + \omega_n^{nk} ωnkS(ωnk)=ωnk+ωn2k+⋯+ωn(n−1)k+ωnnk
所以
若 k ≠ 0 k\not= 0 k=0
则
S ( ω n k ) = 0 1 − ω n k = 0 S(\omega_n^k) = \frac{0}{1 - \omega_n^k} = 0 S(ωnk)=1−ωnk0=0
若 k = 0 k=0 k=0
则
S ( ω n k ) = n S(\omega_n^k) = n S(ωnk)=n
引理2
n c k = ∑ i = 0 n − 1 y i ( w n − k ) i nc_k = \sum_{i=0}^{n-1} y_i (w_n^{-k})^i nck=i=0∑n−1yi(wn−k)i
证明
∑ i = 0 n − 1 y i ( w n − k ) i \sum_{i=0}^{n-1} y_i (w_n^{-k})^i i=0∑n−1yi(wn−k)i
= ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 c j ( w n i ) j ) ( w n − k ) i =\sum_{i=0}^{n-1} (\sum_{j=0}^{n-1} c_j {(w_n^i)}^{j}) (w_n^{-k})^i =i=0∑n−1(j=0∑n−1cj(wni)j)(wn−k)i
= ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 c j ( w n j ) i ) ( w n − k ) i =\sum_{i=0}^{n-1} (\sum_{j=0}^{n-1} c_j {(w_n^j)}^{i}) (w_n^{-k})^i =i=0∑n−1(j=0∑n−1cj(wnj)i)(wn−k)i
= ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 c j ( w n j − k ) i ) =\sum_{i=0}^{n-1} (\sum_{j=0}^{n-1} c_j {(w_n^{j - k})}^{i}) =i=0∑n−1(j=0∑n−1cj(wnj−k)i)
= ∑ j = 0 n − 1 c j ∑ i = 0 n − 1 ( w n j − k ) i =\sum_{j=0}^{n-1} c_j \sum_{i=0}^{n-1} (w_n^{j-k})^{i} =j=0∑n−1cji=0∑n−1(wnj−k)i
= ∑ j = 0 n − 1 c j S ( w n j − k ) =\sum_{j=0}^{n-1} c_j S(w_n^{j-k}) =j=0∑n−1cjS(wnj−k)
= n c k =nc_k =nck
证毕
设
D ( x ) = ∑ i = 0 n − 1 y i x i D(x) = \sum_{i=0}^{n-1} y_i x^i D(x)=i=0∑n−1yixi
则
c k = D ( w n − k ) n c_k = \frac{D(w_n^{-k})}{n} ck=nD(wn−k)
所以再做一次快速傅里叶变换即可
实现 实现 实现
#include <bits/stdc++.h>
using namespace std;
const int N = 400010;
const double PI = acos(-1);
int n, m, tot;
int rev[N];
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[N], b[N];
void fft(Complex a[], int inv)
{
for (int i = 0; i < tot; i ++ )
if (i < rev[i])
swap(a[i], a[rev[i]]);
for (int mid = 1; mid < tot; mid <<= 1)
{
Complex w1 = {cos(PI / mid), inv * sin(PI / mid)};
for (int i = 0; i < tot; i += mid * 2)
{
Complex wk = {1, 0};
for (int j = 0; j < mid; j ++ , wk = wk * w1)
{
Complex x = a[i + j], y = wk * a[i + j + mid];
a[i + j] = x + y, a[i + j + mid] = x - y;
}
}
}
}
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);
int bit = 0;
while ((1 << bit) <= n + m) bit ++ ;
tot = 1 << bit;
for (int i = 0; i < tot; i ++ )
rev[i] = (rev[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));
return 0;
}