多项式卷积 FFT 和 NTT
FFT
多项式
定义
多项式是形如 A ( x ) = ∑ i = 0 n a i × x i A(x) = \sum_{i = 0}^n a_i \times x^i A(x)=∑i=0nai×xi 的表达式,其中 x x x 为不定元,这是多项式的系数表示法。
将多项式中不定元的最大次数称为多项式的次数,记做 ∂ ( A ( x ) ) \partial(A(x)) ∂(A(x))。
运算
A ( x ) = ∑ i = 0 n a i x i , B ( x ) = ∑ i = 0 m b i x i A ( x ) ± B ( x ) = ∑ i = 0 max { n , m } ( a i ± b i ) x i A ( x ) × B ( x ) = ∑ i = 0 n ∑ j = 0 m ( a i × b j ) x i + j = ∑ k = 0 n + m ∑ i = 0 n ( a i × b k − i ) x k \begin{array}{c} A(x)=\sum_{i=0}^{n} a_{i} x^{i}, B(x)=\sum_{i=0}^{m} b_{i} x^{i} \\ \ \\ A(x) \pm B(x)=\sum_{i=0}^{\max \{n, m\}}\left(a_{i} \pm b_{i}\right) x^{i} \\ \ \\ A(x) \times B(x)=\sum_{i=0}^{n} \sum_{j=0}^{m}\left(a_{i} \times b_{j}\right) x^{i+j} \\ =\sum_{k=0}^{n+m} \sum_{i=0}^{n}\left(a_{i} \times b_{k-i}\right) x^{k} \end{array} A(x)=∑i=0naixi,B(x)=∑i=0mbixi A(x)±B(x)=∑i=0max{n,m}(ai±bi)xi A(x)×B(x)=∑i=0n∑j=0m(ai×bj)xi+j=∑k=0n+m∑i=0n(ai×bk−i)xk
多项式乘法满足交换律,结合律和分配率。
多项式的除法通常指带余除法。
A ( x ) = q ( x ) B ( x ) + r ( x ) ∂ ( r ( x ) ) < ∂ ( B ( x ) ) A ( x ) ≡ r ( x ) ( m o d B ) ( x ) A(x) = q(x)B(x)+r(x) \quad \partial(r(x)) < \partial(B(x)) \\ \ \\ A(x) \equiv r(x) \pmod B(x) A(x)=q(x)B(x)+r(x)∂(r(x))<∂(B(x)) A(x)≡r(x)(modB)(x)
q ( x ) q(x) q(x) 称为 B ( x ) B(x) B(x) 除 A ( x ) A(x) A(x) 的商式, r ( x ) r(x) r(x) 称为 B ( x ) B(x) B(x) 除 A ( x ) A(x) A(x) 的余式。若 r ( x ) = 0 r(x)=0 r(x)=0,则称 B ( x ) B(x) B(x) 整除 A ( x ) A(x) A(x),记作 B ( x ) ∣ A ( x ) B(x)|A(x) B(x)∣A(x)。
点值表示法
n n n 个点可以确定唯一的 n − 1 n-1 n−1 次多项式。如果多项式 f ( x ) f(x) f(x) 和 g ( x ) g(x) g(x) 的次数不超过 n n n,且对于 n + 1 n+1 n+1 个不同的数都有相同的对应值,则 f ( x ) = g ( x ) f(x) = g(x) f(x)=g(x)。
如果选取 n + 1 n+1 n+1 个不同的数, x 0 ∼ x n x_0 \sim x_n x0∼xn 对多项式求值,得到 A ( x 0 ) , A ( x 1 ) , ⋯ , A ( x n ) A(x_0), A(x_1), \cdots, A(x_n) A(x0),A(x1),⋯,A(xn)。那么称 { ( x i , A ( x i ) ∣ 0 ≤ i ≤ n , i ∈ N } \{ (x_i,A(x_i) | 0 \leq i \leq n, i \in\mathbb{N}\} {(xi,A(xi)∣0≤i≤n,i∈N} 为多项式 A ( x ) A(x) A(x) 的点值表示。通过系数表示求点值表示的过程叫做求值,通过点值表示求系数表示的过程叫做插值。
设 A ( x ) A(x) A(x) 和 B ( x ) B(x) B(x) 是两个多项式,它们在 x 0 ∼ s n x_0 \sim s_n x0∼sn 上的取值分别为 c 0 ∼ c n c_0 \sim c_n c0∼cn 和 D 0 ∼ D n D_0 \sim D_n D0∼Dn。那么 A ( x ) × B ( x ) A(x) \times B(x) A(x)×B(x) 在 x 0 , x 1 , … , x n x_{0}, x_{1}, \ldots, x_{n} x0,x1,…,xn 上的取值分别为 c 0 D 0 ∼ c n D n c_{0} D_{0} \sim c_n D_n c0D0∼cnDn。
复数
- 定义
虚数单位 i = − 1 i=\sqrt{-1} i=−1。
形如 a + b i ( a , b ∈ R ) a + b_i (a, b \in \mathbb{R}) a+bi(a,b∈R) 的数称为复数, a a a 称为实部, b b b 称为虚部。全体复数的集合记为 C \mathbb{C} C。
- 运算
设 z = a + b i , w = c + d i z = a + bi, w = c+di z=a+bi,w=c+di,有
z ± w = ( a ± c ) + ( b ± d ) i z × w = a c + a d i + b c i + b d i 2 = ( a c − b d ) + ( a d + b c ) i z ÷ w = a + b i c + d i = ( a + b i ) ( c − d i ) ( c + d i ) ( c − d i ) = ( a c + b d ) + ( b c − a d ) i c 2 + d 2 z \pm w=(a \pm c)+(b \pm d) i \\ \ \\ z \times w=a c+a d i+b c i+b d i^{2}=(a c-b d)+(a d+b c) i \\ \ \\ z \div w=\frac{a+b i}{c+d i}=\frac{(a+b i)(c-d i)}{(c+d i)(c-d i)}=\frac{(a c+b d)+(b c-a d) i}{c^{2}+d^{2}} z±w=(a±c)+(b±d)i z×w=ac+adi+bci+bdi2=(ac−bd)+(ad+bc)i z÷w=c+dia+bi=(c+di)(c−di)(a+bi)(c−di)=c2+d2(ac+bd)+(bc−ad)i
复数的相加满足平行四边形法则。

共轭复数: z ‾ = a − b i \overline{z}=a-b i z=a−bi,相当于对虚部取反。
- 几何意义
令横轴为实轴,纵轴为虚轴。复数 a + b i a+bi a+bi 就对应坐标为 ( a , b ) (a,b) (a,b) 的点或向量。
复数对应的点到原点的距离称为复数的模长,记为 r = ∣ z ∣ r=|z| r=∣z∣。 由勾股定理 ∣ z ∣ = a 2 + b 2 = z z ‾ |z|=\sqrt{a^{2}+b^{2}}=\sqrt{z \overline{z}} ∣z∣=a2+b2=zz
复数与实轴正半轴形成的角称为复数的辐角,记为 φ = arg z \varphi=\arg z φ=argz。
复数也可以用模长和辐角表示,称为复数的极坐标形式。由极坐标与直角坐标的转化关系, z = r ( cos φ + i sin φ ) z=r(\cos \varphi+i \sin \varphi) z=r(cosφ+isinφ)。

复数的乘法,按照模长相乘,辐角相加的运算规则。除法为乘法的逆运算。
复数的共轭,则为复数对应向量与实轴轴对称的结果。
单位根
欧拉公式
对于任意实数 x x x,都有
e i x = cos x + i sin x e^{i x}=\cos x+i \sin x eix=cosx+isinx
证明略。这个公式可以说明当
x
x
x 为实数时,函数
f
(
x
)
=
e
i
x
f(x) = e^{ix}
f(x)=eix 可以在复数平面表述一个单位元,且
x
x
x 为平面上的一个幅角。

定义
将方程 z n = 1 z^n = 1 zn=1 的所有复数根称为 n n n 次单位根,这样的单位根共有 n n n 个。
e 2 π k i n ( k = 0 , 1 , ⋯ , n − 1 ) \large e^{\frac{2 \pi k i}{n}} \normalsize \quad(k = 0, 1, \cdots,n-1 ) en2πki(k=0,1,⋯,n−1)
这些单位根分布在复数平面的单位圆上,构成了一个正 n n n 边形,它们把单位圆等分成 n n n 个部分。

简单的,定义
ω
=
e
2
π
k
i
n
\omega = e^{\frac{2 \pi k i}{n}}
ω=en2πki,若有
n
n
n 与
k
k
k 互质,则
ω
\omega
ω 称为
n
n
n 次本原单位根。
w
x
(
x
=
0
,
1
,
⋯
,
n
−
1
)
w^x\quad (x = 0, 1, \cdots, n-1)
wx(x=0,1,⋯,n−1) 构成了所有的
n
n
n 次单位根。特殊的,当
k
=
1
k=1
k=1 时,本原单位根记为
ω
n
\omega_{n}
ωn。
性质
w n n = 1 ω n 0 , ω n 1 , ⋯ , ω n n − 1 互 不 相 同 ω n k = cos 2 π k n + i sin 2 π k n ω n k = ω 2 n 2 k ω n n 2 = e i π = − 1 ω n k + n 2 = − ω n k 单 位 根 的 共 轭 复 数 为 其 倒 数 w_n^n = 1 \\ \ \\ \omega_{n}^{0}, \omega_{n}^{1}, \cdots, \omega_{n}^{n-1} \quad 互不相同\\ \ \\ \omega_{n}^{k}=\cos \frac{2 \pi k}{n} +i \sin \frac{2 \pi k}{n} \\ \ \\ \omega_{n}^{k}=\omega_{2 n}^{2 k} \\ \ \\ \omega_{n}^{\frac{n}{2}}=e^{i \pi}=-1 \\ \ \\ \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k}\\ \ \\ 单位根的共轭复数为其倒数 wnn=1 ωn0,ωn1,⋯,ωnn−1互不相同 ωnk=cosn2πk+isinn2πk ωnk=ω2n2k ωn2n=eiπ=−1 ωnk+2n=−ωnk 单位根的共轭复数为其倒数
Cooley-Tukey 算法
离散傅里叶变换 DFT
多项式有两个表示方法——系数表示法和点值表示法。它们各有千秋,系数表示法适用范围广,但乘法操作为 O ( n 2 ) O(n^2) O(n2);点值表示法适用范围窄,但乘法操作为线性。那么能不能在两个表示法相互转换呢?
假设现在有一个 n − 1 n-1 n−1 次多项式, A ( x ) = ∑ i = 0 n a i × x i A(x) = \sum_{i = 0}^n a_i \times x_i A(x)=∑i=0nai×xi。为了方便,设 n = 2 m , m ∈ N n = 2^m, m \in \mathbb{N} n=2m,m∈N,不足在高位补 0 0 0。
将 n n n 个 n n n 次单位根 ω n 0 , ω n 1 , … , ω n n − 1 \omega_{n}^{0}, \omega_{n}^{1}, \ldots, \omega_{n}^{n-1} ωn0,ωn1,…,ωnn−1 代入 A ( x ) A(x) A(x) 将其转换成点值表达 A ( ω n k ) = ∑ i = 0 n − 1 a i ω k i A\left(\omega_{n}^{k}\right)=\sum_{i=0}^{n-1} a_{i} \omega^{k i} A(ωnk)=∑i=0n−1aiωki。 点值向量 y ⃗ = ( A ( ω n 0 ) , A ( ω n 1 ) , … , A ( ω n n − 1 ) ) \vec{y}=\left(A\left(\omega_{n}^{0}\right), A\left(\omega_{n}^{1}\right), \ldots, A\left(\omega_{n}^{n-1}\right)\right) y=(A(ωn0),A(ωn1),…,A(ωnn−1)) 称为系数向量 a ⃗ = ( a 0 , a 1 , … , a n − 1 ) \vec{a}=\left(a_{0}, a_{1}, \ldots, a_{n-1}\right) a=(a0,a1,…,an−1) 的离散傅里叶变换,记为 y ⃗ = DFT n ( a ⃗ ) \vec{y}= \operatorname{DFT}_{n}(\vec{a}) y=DFTn(a)。
可以发现离散傅里叶变换的复杂度为 O ( n 2 ) O(n^2) O(n2) 的。Cooley-Tukey 算法对 DFT 进行了优化,它将每一项进行了奇偶分类。当 k < n 2 k < \frac{n}{2} k<2n 时
根据性质 ω n k = ω 2 n 2 k \omega_{n}^{k}=\omega_{2 n}^{2 k} ωnk=ω2n2k
A
(
ω
n
k
)
=
∑
i
=
0
n
−
1
a
i
ω
n
k
i
=
∑
i
=
0
n
−
1
a
2
i
ω
n
2
k
i
+
ω
n
k
∑
i
=
0
n
2
−
1
a
2
i
+
1
ω
n
2
k
i
=
∑
i
=
0
n
2
−
1
a
2
i
ω
n
2
k
i
+
ω
n
k
∑
i
=
0
n
2
−
1
a
2
i
+
1
ω
n
2
k
i
A\left(\omega_{n}^{k}\right) =\sum_{i=0}^{n-1} a_{i} \omega_{n}^{k i} \\ =\sum_{i=0}^{n-1} a_{2 i} \omega_{n}^{2 k i}+\omega_{n}^{k} \sum_{i=0}^{\frac{n}{2}-1} a_{2 i+1} \omega_{n}^{2 k i} \\ = \sum_{i=0}^{\frac{n}{2}-1} a_{2 i} \omega_{\frac{n}{2}}^{k i}+\omega_{n}^{k} \sum_{i=0}^{\frac{n}{2}-1} a_{2 i+1} \omega_{\frac{n}{2}}^{k i}
A(ωnk)=i=0∑n−1aiωnki=i=0∑n−1a2iωn2ki+ωnki=0∑2n−1a2i+1ωn2ki=i=0∑2n−1a2iω2nki+ωnki=0∑2n−1a2i+1ω2nki
根据性质
w
n
n
=
1
w_n^n = 1
wnn=1 和
ω
n
k
+
n
2
=
−
ω
n
k
\omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k}
ωnk+2n=−ωnk
A ( ω n k + n 2 ) = ∑ i = 0 n 2 − 1 a 2 i ω n 2 k i + n 2 × i + ω n k + n 2 ∑ i = 0 n 2 − 1 a 2 i + 1 ω n 2 k i = ∑ i = 0 n 2 − 1 a 2 i ω n 2 k i − ω n k ∑ i = 0 n 2 − 1 a 2 i + 1 ω n 2 k i \begin{aligned} A\left(\omega_{n}^{k+\frac{n}{2}}\right) &=\sum_{i=0}^{\frac{n}{2}-1} a_{2 i} \omega_{\frac{n}{2}}^{ki + \frac{n}{2} \ \times i}+\omega_{n}^{k+\frac{n}{2}} \sum_{i=0}^{\frac{n}{2}-1} a_{2 i+1} \omega_{\frac{n}{2}}^{k i} \\ &=\sum_{i=0}^{\frac{n}{2}-1} a_{2 i} \omega_{\frac{n}{2}}^{k i}-\omega_{n}^{k} \sum_{i=0}^{\frac{n}{2}-1} a_{2 i+1} \omega_{\frac{n}{2}}^{k i} \end{aligned} A(ωnk+2n)=i=0∑2n−1a2iω2nki+2n ×i+ωnk+2ni=0∑2n−1a2i+1ω2nki=i=0∑2n−1a2iω2nki−ωnki=0∑2n−1a2i+1ω2nki
可以发现左右两个式子相似。令左式的系数表达式为 A 1 A_1 A1,右式的系数表达式为 A 2 A_2 A2。
A ( ω n k ) = A 1 ( ω n 2 k ) + A 2 ( ω n 2 k ) A ( ω n k + n 2 ) = A 1 ( ω n 2 k ) − A 2 ( ω n 2 k ) A\left(\omega_{n}^{k}\right) = A_1(\omega_{\frac{n}{2}}^k) + A_2(\omega_{\frac{n}{2}}^k) \\ \ \\ A\left(\omega_{n}^{k+\frac{n}{2}}\right) = A_1(\omega_{\frac{n}{2}}^k) - A_2(\omega_{\frac{n}{2}}^k) A(ωnk)=A1(ω2nk)+A2(ω2nk) A(ωnk+2n)=A1(ω2nk)−A2(ω2nk)
这意味着,如果能求出 A 1 ( ω n 2 k ) A_1(\omega_{\frac{n}{2}}^k) A1(ω2nk) 和 A 2 ( ω n 2 k ) A_2(\omega_{\frac{n}{2}}^k) A2(ω2nk),那么可以直接求出 A ( ω n k ) A\left(\omega_{n}^{k}\right) A(ωnk) 和 A ( ω n k + n 2 ) A\left(\omega_{n}^{k+\frac{n}{2}}\right) A(ωnk+2n)。
于是,我们只要算出 a 0 , a 2 , … , a n 2 a_{0}, a_{2}, \ldots, a_{\frac{n}{2}} a0,a2,…,a2n 和 a 1 , a 3 , … , a n 2 − 1 a_{1}, a_{3}, \ldots, a_{\frac{n}{2} - 1} a1,a3,…,a2n−1 的 DFT 就可以算出 a 0 , a 1 , … a n a_{0}, a_{1}, \ldots a_{n} a0,a1,…an 的 DFT,把问题规模缩小了一半,递归下去即可。时间复杂度 O ( n log 2 n ) O\left(n \log _{2} n\right) O(nlog2n)。
傅里叶逆变换 IDFT
刚刚计算的是 y ⃗ = DFT n ( a ⃗ ) , \vec{y}=\operatorname{DFT}_{n}(\vec{a}), y=DFTn(a), 可以将多项式转化成点值表示,现在为了将点值表示转化成系数表示,需要计算 IDFT,它是 DFT 的逆运算,也就是插值的过程。提起插值,可能先想起拉格朗日插值,不过 FFT 不需要它。
考虑解一个方程组
{ a 0 ( ω n 0 ) 0 + ⋯ + a n − 2 ( ω n 0 ) n − 2 + a n − 1 ( ω n 0 ) n − 1 = A ( ω n 0 ) a 0 ( ω n 1 ) 0 + ⋯ + a n − 2 ( ω n 1 ) n − 2 + a n − 1 ( ω n 1 ) n − 1 = A ( ω n 1 ) ⋮ ⋮ ⋮ ⋮ a 0 ( ω n n − 1 ) 0 + ⋯ + a n − 2 ( ω n n − 1 ) n − 2 + a n − 1 ( ω n n − 1 ) n − 1 = A ( ω n n − 1 ) \left\{\begin{array}{ccccc} a_{0}\left(\omega_{n}^{0}\right)^{0} & +\cdots & +a_{n-2}\left(\omega_{n}^{0}\right)^{n-2} & +a_{n-1}\left(\omega_{n}^{0}\right)^{n-1} & =A\left(\omega_{n}^{0}\right) \\ a_{0}\left(\omega_{n}^{1}\right)^{0} & +\cdots & +a_{n-2}\left(\omega_{n}^{1}\right)^{n-2} & +a_{n-1}\left(\omega_{n}^{1}\right)^{n-1} & =A\left(\omega_{n}^{1}\right) \\ \vdots & \vdots & \vdots & \vdots \\ a_{0}\left(\omega_{n}^{n-1}\right)^{0} & +\cdots & +a_{n-2}\left(\omega_{n}^{n-1}\right)^{n-2} & +a_{n-1}\left(\omega_{n}^{n-1}\right)^{n-1} & =A\left(\omega_{n}^{n-1}\right) \end{array}\right. ⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧a0(ωn0)0a0(ωn1)0⋮a0(ωnn−1)0+⋯+⋯⋮+⋯+an−2(ωn0)n−2+an−2(ωn1)n−2⋮+an−2(ωnn−1)n−2+an−1(ωn0)n−1+an−1(ωn1)n−1⋮+an−1(ωnn−1)n−1=A(ωn0)=A(ωn1)=A(ωnn−1)
写成矩阵的形式
[ ( ω n 0 ) 0 ( ω n 0 ) 1 ⋯ ( ω n 0 ) n − 1 ( ω n 1 ) 0 ( ω n 1 ) 1 ⋯ ( ω n 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n n − 1 ) 0 ( ω n n − 1 ) 1 ⋯ ( ω n n − 1 ) n − 1 ] [ a 0 a 1 ⋮ a n − 1 ] = [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[\begin{array}{cccc} \left(\omega_{n}^{0}\right)^{0} & \left(\omega_{n}^{0}\right)^{1} & \cdots & \left(\omega_{n}^{0}\right)^{n-1} \\ \left(\omega_{n}^{1}\right)^{0} & \left(\omega_{n}^{1}\right)^{1} & \cdots & \left(\omega_{n}^{1}\right)^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ \left(\omega_{n}^{n-1}\right)^{0} & \left(\omega_{n}^{n-1}\right)^{1} & \cdots & \left(\omega_{n}^{n-1}\right)^{n-1} \end{array}\right]\left[\begin{array}{c} a_{0} \\ a_{1} \\ \vdots \\ a_{n-1} \end{array}\right]=\left[\begin{array}{c} A\left(\omega_{n}^{0}\right) \\ A\left(\omega_{n}^{1}\right) \\ \vdots \\ A\left(\omega_{n}^{n-1}\right) \end{array}\right] ⎣⎢⎢⎢⎢⎡(ωn0)0(ωn1)0⋮(ωnn−1)0(ωn0)1(ωn1)1⋮(ωnn−1)1⋯⋯⋱⋯(ωn0)n−1(ωn1)n−1⋮(ωnn−1)n−1⎦⎥⎥⎥⎥⎤⎣⎢⎢⎢⎡a0a1⋮an−1⎦⎥⎥⎥⎤=⎣⎢⎢⎢⎡A(ωn0)A(ωn1)⋮A(ωnn−1)⎦⎥⎥⎥⎤
左矩阵即为系数矩阵 V V V,要求它的逆矩阵 V − 1 V^{-1} V−1。可以发现,这个矩阵可以使用范德蒙德矩阵的逆矩阵公式,令 D D D 为 V V V 的伴随矩阵。
V − 1 = 1 n D = 1 n [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) 0 ( ω n − 1 ) 1 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ ( ω n − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] {V}^{-1}=\frac{1}{n} {D}=\frac{1}{n}\left[\begin{array}{cccc} \left(\omega_{n}^{-0}\right)^{0} & \left(\omega_{n}^{-0}\right)^{1} & \cdots & \left(\omega_{n}^{-0}\right)^{n-1} \\ \left(\omega_{n}^{-1}\right)^{0} & \left(\omega_{n}^{-1}\right)^{1} & \cdots & \left(\omega_{n}^{-1}\right)^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ \left(\omega_{n}^{-(n-1)}\right)^{0} & \left(\omega_{n}^{-(n-1)}\right)^{1} & \cdots & \left(\omega_{n}^{-(n-1)}\right)^{n-1} \end{array}\right] V−1=n1D=n1⎣⎢⎢⎢⎢⎢⎡(ωn−0)0(ωn−1)0⋮(ωn−(n−1))0(ωn−0)1(ωn−1)1⋮(ωn−(n−1))1⋯⋯⋱⋯(ωn−0)n−1(ωn−1)n−1⋮(ωn−(n−1))n−1⎦⎥⎥⎥⎥⎥⎤
证明较为复杂,我们简单验证一下,设 E = V × D E = V \times D E=V×D,则有
E i , j = ∑ k = 0 n − 1 D i k V k j = ∑ k = 0 n − 1 ω n − i k ω n k j = ∑ k = 0 n − 1 ω n k ( j − i ) E_{i, j}=\sum_{k=0}^{n-1} D_{i k} V_{k j}=\sum_{k=0}^{n-1} \omega_{n}^{-i k} \omega_{n}^{k j}=\sum_{k=0}^{n-1} \omega_{n}^{k(j-i)} Ei,j=k=0∑n−1DikVkj=k=0∑n−1ωn−ikωnkj=k=0∑n−1ωnk(j−i)
当 i = j i=j i=j 时,显然 E i , j = n E_{i,j}=n Ei,j=n。当 i ≠ j i \neq j i=j 时,有
E i , j = ∑ k = 0 n − 1 ( ω n j − i ) k = 1 − ( ω n j − i ) n 1 − ω n j − i = 0 E_{i, j}=\sum_{k=0}^{n-1}\left(\omega_{n}^{j-i}\right)^{k}=\frac{1-\left(\omega_{n}^{j-i}\right)^{n}}{1-\omega_{n}^{j-i}}=0 Ei,j=k=0∑n−1(ωnj−i)k=1−ωnj−i1−(ωnj−i)n=0
因此
D
D
D 的主对角线都是
n
n
n,其它位置都是
0
0
0,除以
n
n
n 后即为单位矩阵,证毕。
综上所述,可以得到
[ a 0 a 1 ⋮ a n − 1 ] = 1 n [ ( ω n − 0 ) 0 ( ω n − 0 ) 1 ⋯ ( ω n − 0 ) n − 1 ( ω n − 1 ) 0 ( ω n − 1 ) 1 ⋯ ( ω n − 1 ) n − 1 ⋮ ⋮ ⋱ ⋮ − ( n − 1 ) ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] \left[\begin{array}{c} a_{0} \\ a_{1} \\ \vdots \\ a_{n-1} \end{array}\right]=\frac{1}{n}\left[\begin{array}{cccc} \left(\omega_{n}^{-0}\right)^{0} & \left(\omega_{n}^{-0}\right)^{1} & \cdots & \left(\omega_{n}^{-0}\right)^{n-1} \\ \left(\omega_{n}^{-1}\right)^{0} & \left(\omega_{n}^{-1}\right)^{1} & \cdots & \left(\omega_{n}^{-1}\right)^{n-1} \\ \vdots & \vdots & \ddots & \vdots \\ -(n-1) & \left(\omega_{n}^{-(n-1)}\right)^{1} & \cdots & \left(\omega_{n}^{-(n-1)}\right)^{n-1} \end{array}\right]\left[\begin{array}{c} A\left(\omega_{n}^{0}\right) \\ A\left(\omega_{n}^{1}\right) \\ \vdots \\ A\left(\omega_{n}^{n-1}\right) \end{array}\right] ⎣⎢⎢⎢⎡a0a1⋮an−1⎦⎥⎥⎥⎤=n1⎣⎢⎢⎢⎢⎢⎡(ωn−0)0(ωn−1)0⋮−(n−1)(ωn−0)1(ωn−1)1⋮(ωn−(n−1))1⋯⋯⋱⋯(ωn−0)n−1(ωn−1)n−1⋮(ωn−(n−1))n−1⎦⎥⎥⎥⎥⎥⎤⎣⎢⎢⎢⎡A(ωn0)A(ωn1)⋮A(ωnn−1)⎦⎥⎥⎥⎤
IDFT 就相当于把 DFT 过程中的 w n i w_n^i wni 换成 w n − i w_n^{-i} wn−i,即为共轭复数。然后做一次 DFT,将结果除以 n n n 即可。
递归实现
#include <bits/stdc++.h>
using namespace std;
const int N = 5e6 + 5, INF = 0x3f3f3f3f; //一定要把 N 开到 5 倍
inline int read() {
int x = 0, f = 0; char ch = 0;
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
typedef complex <double> E;
const double pi = acos(-1); //π
void fft(E *a, int mid, int flg) { //单位根共轭的 flg
if (mid == 0) return ;
E a1[mid], a2[mid]; //奇偶分类
for (int i = 0; i < mid; i++) a1[i] = a[i << 1], a2[i] = a[i << 1 | 1]; //必须 < mid
fft(a1, mid >> 1, flg); fft(a2, mid >> 1, flg); //递归调用数组次数太多
for (int i = 0; i < mid; i++) {
E w(cos(pi * i / mid), flg * sin(pi * i / mid)); //单位根的 2 可以被 n / 2 = mid 约去
a[i] = a1[i] + w * a2[i], a[i + mid] = a1[i] - w * a2[i];
}
}
E a[N], b[N];
int main() {
int n = read(), m = read();
for (int i = 0; i <= n; i++) a[i] = read(); //实部
for (int i = 0; i <= m; i++) b[i] = read();
for (m += n, n = 1; n <= m; n <<= 1); // 把长度补到 2 的幂
fft(a, n >> 1, 1); fft(b, n >> 1, 1); //DFT
for (int i = 0; i < n; i++) a[i] *= b[i]; //n 个点值
fft(a, n >> 1, -1); //IDFT
for (int i = 0; i <= m; i++) { //循环到 m
printf("%.0f ", fabs(a[i].real()) / n); //除以 n 要小心 -0
}
return 0;
}

迭代实现

观察递归的观察,可以发现,最后的运算顺序,是按二进制位反转之后从小到大排序的。因此我们只需要实现一个二进制反转然后排序,这样差为
n
2
\frac{n}{2}
2n 的元素就在一组了。
具体的,模拟从高位到低位的二进制加法。维护当前下标 i i i 和反相加的下标 j j j,表示反转后下标 i i i 应放反转前下标 j j j 的东西。如果 i > j i >j i>j,就将 i i i 和 j j j 存的东西交换,这样就能得到所需的数组。
#include <bits/stdc++.h>
using namespace std;
const int N = 5e6 + 5, INF = 0x3f3f3f3f; //一定要把 N 开到 5 倍
inline int read() {
int x = 0, f = 0; char ch = 0;
while (!isdigit(ch)) f |= ch == '-', ch = getchar();
while (isdigit(ch)) x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar();
return f ? -x : x;
}
typedef complex <double> E; //手写会更快
const double pi = acos(-1); //π
void fft(E *a, int n, int flg) {
for (int i = 0, j = 0; i < n; i++) {
if (i < j) swap(a[i], a[j]);
for (int k = n >> 1; (j ^= k) < k; k >>= 1);
}
for (int i = 2; i <= n; i <<= 1) //待合并的区间长度
for (int j = 0, mid = (i >> 1); j < n; j += i) {
E wn(cos(pi / mid), flg * sin(pi / mid)) /*本原单位根*/, w(1, 0) /*幂*/;
for (int k = 0; k < mid; k++, w *= wn) {
//E w(cos(pi * k / mid), flg * sin(pi * k / mid)); n log n 次 cos 和 sin 也会超时
E x = a[j + k + mid] * w;
a[j + k + mid] = a[j + k] - x, a[j + k] = a[j + k] + x; //合并的顺序一定不能换
}
}
}
E a[N], b[N];
int main() {
int n = read(), m = read();
for (int i = 0; i <= n; i++) a[i] = read(); //实部
for (int i = 0; i <= m; i++) b[i] = read();
for (m += n, n = 1; n <= m; n <<= 1); // 把长度补到 2 的幂
fft(a, n, 1); fft(b, n, 1); //DFT
for (int i = 0; i < n; i++) a[i] *= b[i]; //n 个点值
fft(a, n, -1); //IDFT
for (int i = 0; i <= m; i++) { //循环到 m
printf("%.0f ", fabs(a[i].real()) / n); //除以 n 要小心 -0
}
return 0;
}

NTT
原根
review 一下 FFT 中单位根的一些性质
w n n = 1 ω n 0 , ω n 1 , ⋯ , ω n n − 1 互 不 相 同 ω n k + n 2 = − ω n k w_n^n = 1 \\ \ \\ \omega_{n}^{0}, \omega_{n}^{1}, \cdots, \omega_{n}^{n-1} \quad 互不相同\\ \ \\ \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k}\\ wnn=1 ωn0,ωn1,⋯,ωnn−1互不相同 ωnk+2n=−ωnk
根据费马小定理,任意一个质数 p p p,有
a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap−1≡1(modp)
定义 p p p 的原根 g g g 为使得 g 0 , g 1 , ⋯ , g p − 2 m o d p g^0, g^1, \cdots,g^{p-2} \bmod p g0,g1,⋯,gp−2modp 互不相同的数。
如果一个质数 p p p 能表示为 p = k × 2 n + 1 p = k \times 2^n + 1 p=k×2n+1,并找到它的原根 g g g。令 g n = g k m o d p g_n = g^k \bmod p gn=gkmodp,这样可以使得 g n 0 , g n 1 , ⋯ , g n p − 2 m o d p g_n^0, g_n^1, \cdots,g_n^{p-2} \bmod p gn0,gn1,⋯,gnp−2modp 互不相同,且有 g n n ≡ 1 ( m o d p ) g_n^n \equiv 1 \pmod p gnn≡1(modp)。又因为 g n k g_n^k gnk 互不相同,所以 g n n 2 ≡ − 1 ( m o d p ) g_{n}^{\frac{n}{2}} \equiv-1 \pmod p gn2n≡−1(modp)
所以在 NTT 中,可以用原根代替单位根,精度更高常数更小。不足之处是对模数有限制。这一点可以使用任意模数 NTT,因为过于毒瘤,本文不作讲解。
998244353 = 119 ⋅ 2 23 + 1 , g = 3 1004535809 = 479 ⋅ 2 21 + 1 , g = 3 \begin{array}{l} 998244353=119 \cdot 2^{23}+1, g=3 \\ 1004535809=479 \cdot 2^{21}+1, g=3 \end{array} 998244353=119⋅223+1,g=31004535809=479⋅221+1,g=3

浙公网安备 33010602011771号