多项式卷积 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=0nj=0m(ai×bj)xi+j=k=0n+mi=0n(ai×bki)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 n1 次多项式。如果多项式 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 x0xn 对多项式求值,得到 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)0in,iN} 为多项式 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 x0sn 上的取值分别为 c 0 ∼ c n c_0 \sim c_n c0cn D 0 ∼ D n D_0 \sim D_n D0Dn。那么 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 c0D0cnDn

复数

  • 定义

虚数单位 i = − 1 i=\sqrt{-1} i=1

形如 a + b i ( a , b ∈ R ) a + b_i (a, b \in \mathbb{R}) a+bi(a,bR) 的数称为复数, 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=(acbd)+(ad+bc)i z÷w=c+dia+bi=(c+di)(cdi)(a+bi)(cdi)=c2+d2(ac+bd)+(bcad)i

复数的相加满足平行四边形法则。

共轭复数: z ‾ = a − b i \overline{z}=a-b i z=abi,相当于对虚部取反。

  • 几何意义

令横轴为实轴,纵轴为虚轴。复数 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,,n1)

这些单位根分布在复数平面的单位圆上,构成了一个正 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,,n1) 构成了所有的 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,,ωnn1 ω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 n1 次多项式, 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,mN,不足在高位补 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,,ωnn1 代入 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=0n1aiω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(ωnn1)) 称为系数向量 a ⃗ = ( a 0 , a 1 , … , a n − 1 ) \vec{a}=\left(a_{0}, a_{1}, \ldots, a_{n-1}\right) a =(a0,a1,,an1) 的离散傅里叶变换,记为 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=0n1aiωnki=i=0n1a2iωn2ki+ωnki=02n1a2i+1ωn2ki=i=02n1a2iω2nki+ωnki=02n1a2i+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=02n1a2iω2nki+2n ×i+ωnk+2ni=02n1a2i+1ω2nki=i=02n1a2iω2nkiωnki=02n1a2i+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,,a2n1 的 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)0a0(ωnn1)0++++an2(ωn0)n2+an2(ωn1)n2+an2(ωnn1)n2+an1(ωn0)n1+an1(ωn1)n1+an1(ωnn1)n1=A(ωn0)=A(ωn1)=A(ωnn1)

写成矩阵的形式

[ ( ω 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(ωnn1)0(ωn0)1(ωn1)1(ωnn1)1(ωn0)n1(ωn1)n1(ωnn1)n1a0a1an1=A(ωn0)A(ωn1)A(ωnn1)

左矩阵即为系数矩阵 V V V,要求它的逆矩阵 V − 1 V^{-1} V1。可以发现,这个矩阵可以使用范德蒙德矩阵的逆矩阵公式,令 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] V1=n1D=n1(ωn0)0(ωn1)0(ωn(n1))0(ωn0)1(ωn1)1(ωn(n1))1(ωn0)n1(ωn1)n1(ωn(n1))n1

证明较为复杂,我们简单验证一下,设 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=0n1DikVkj=k=0n1ωnikωnkj=k=0n1ωnk(ji)

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=0n1(ωnji)k=1ωnji1(ωnji)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] a0a1an1=n1(ωn0)0(ωn1)0(n1)(ωn0)1(ωn1)1(ωn(n1))1(ωn0)n1(ωn1)n1(ωn(n1))n1A(ωn0)A(ωn1)A(ωnn1)

IDFT 就相当于把 DFT 过程中的 w n i w_n^i wni 换成 w n − i w_n^{-i} wni,即为共轭复数。然后做一次 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,,ωnn1 ωnk+2n=ωnk

根据费马小定理,任意一个质数 p p p,有

a p − 1 ≡ 1 ( m o d p ) a^{p-1} \equiv 1 \pmod p ap11(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,,gp2modp 互不相同的数。

如果一个质数 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,,gnp2modp 互不相同,且有 g n n ≡ 1 ( m o d p ) g_n^n \equiv 1 \pmod p gnn1(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 gn2n1(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=119223+1,g=31004535809=479221+1,g=3

实现

posted @ 2020-08-13 17:21  ylxmf2005  阅读(77)  评论(0)    收藏  举报