快速傅里叶变换与快速数论变换(FFT&NTT)学习笔记
一.前置知识
1.复数
(1)定义式
我们定义 i 2 = − 1 i^2=-1 i2=−1, a ∈ R , b ∈ R a\in \R,b\in \R a∈R,b∈R,则复数 z z z可表示为: z = a + b i z=a+bi z=a+bi
(2)运算法则
设存在两个复数
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
1
,
z
2
z_1,z_2
z1,z2的运算法则如下:
z
1
±
z
2
=
(
a
1
±
a
2
)
+
(
b
1
±
b
2
)
i
z_1\pm z_2=(a_1\pm a_2)+(b_1\pm b_2)i
z1±z2=(a1±a2)+(b1±b2)i
z
1
∗
z
2
=
(
a
1
+
b
1
i
)
(
a
2
+
b
2
i
)
=
(
a
1
a
2
−
b
1
b
2
)
+
(
a
1
b
2
+
a
2
b
1
)
i
z_1*z_2=(a_1+b_1i)(a_2+b_2i)=(a_1a_2-b_1b_2)+(a_1b_2+a_2b_1)i
z1∗z2=(a1+b1i)(a2+b2i)=(a1a2−b1b2)+(a1b2+a2b1)i
z
1
z
2
=
a
1
+
b
1
i
a
2
+
b
2
i
=
(
a
1
+
b
1
i
)
(
a
2
−
b
2
i
)
(
a
2
+
b
2
i
)
(
a
2
−
b
2
i
)
=
(
a
1
a
2
+
b
1
b
2
)
+
(
b
1
a
2
−
a
1
b
2
)
i
a
2
2
+
b
2
2
\frac{z_1}{z_2}=\frac{a_1+b_1i}{a_2+b_2i}=\frac{(a_1+b_1i)(a_2-b_2i)}{(a_2+b_2i)(a_2-b_2i)}=\frac{(a_1a_2+b_1b_2)+(b_1a_2-a_1b_2)i}{a_2^2+b_2^2}
z2z1=a2+b2ia1+b1i=(a2+b2i)(a2−b2i)(a1+b1i)(a2−b2i)=a22+b22(a1a2+b1b2)+(b1a2−a1b2)i
(3)复数类
我们需要实现一个支持 + − × +-\times +−×的复数类。
struct Com{
double x, y;
Com(double _x = 0, double _y = 0){x = _x, y = _y;}
Com operator + (const Com &t)const{return Com(x+t.x, y+t.y);}
Com operator - (const Com &t)const{return Com(x-t.x, y-t.y);}
Com operator * (const Com &t)const{return Com(x*t.x-y*t.y, x*t.y+y*t.x);}
};
2.多项式的两种表示
设有两个多项式:
f
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
.
.
.
+
a
n
−
1
x
n
−
1
f(x)=a_0+a_1x+a_2x^2+...+a_{n-1}x^{n-1}
f(x)=a0+a1x+a2x2+...+an−1xn−1
g
(
x
)
=
b
0
+
b
1
x
+
b
2
x
2
+
.
.
.
+
b
n
−
1
x
n
−
1
g(x)=b_0+b_1x+b_2x^2+...+b_{n-1}x^{n-1}
g(x)=b0+b1x+b2x2+...+bn−1xn−1
设多项式
h
(
x
)
=
f
(
x
)
g
(
x
)
h(x)=f(x)g(x)
h(x)=f(x)g(x)
(1)系数表示
我们称
a
0
,
a
1
,
a
2
.
.
.
a
n
−
1
a_0,a_1,a_2...a_{n-1}
a0,a1,a2...an−1为多项式
f
(
x
)
f(x)
f(x)的系数表示。
若
h
(
x
)
h(x)
h(x)的系数为
c
0
,
c
1
,
.
.
.
,
c
n
−
1
c_0,c_1,...,c_{n-1}
c0,c1,...,cn−1,则:
c
k
=
∑
i
+
j
=
k
=
a
i
b
j
c_k=\sum _{i+j=k}=a_ib_j
ck=i+j=k∑=aibj
计算该式的时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)。
(2)点值表示
当我们把
n
n
n个不同的值
x
0
,
x
1
,
x
2
,
.
.
.
,
x
n
−
1
x_0,x_1,x_2,...,x_{n-1}
x0,x1,x2,...,xn−1,带入到
f
(
x
)
f(x)
f(x),得到
f
(
x
0
)
,
.
.
.
,
f
(
x
n
−
1
)
f(x_0),...,f(x_{n-1})
f(x0),...,f(xn−1)以后,可以通过
n
n
n个方程解出
a
0
,
.
.
.
,
a
n
−
1
a_0,...,a_{n-1}
a0,...,an−1,所以我们也可以用
f
(
x
0
)
,
.
.
.
,
f
(
x
n
−
1
)
f(x_0),...,f(x_{n-1})
f(x0),...,f(xn−1)来表示一个多项式,称为该多项式的点值表示。
如果我们将
2
n
−
1
2n-1
2n−1个不同的
x
0
,
.
.
.
,
x
2
n
−
2
x_0,...,x_{2n-2}
x0,...,x2n−2数分别带入
f
(
x
)
,
g
(
x
)
f(x),g(x)
f(x),g(x),则:
h
(
x
k
)
=
f
(
x
k
)
g
(
x
k
)
h(x_k)=f(x_k)g(x_k)
h(xk)=f(xk)g(xk)
计算该式的时间复杂度为
O
(
n
)
O(n)
O(n)。
2.泰勒展开
(1)用多项式逼近函数
若函数
f
(
x
)
f(x)
f(x)在点
x
0
x_0
x0上可导,则有:
f
(
x
)
=
f
(
x
0
)
+
f
′
(
x
0
)
(
x
−
x
0
)
+
o
(
x
−
x
0
)
f(x)=f(x_0)+f'(x_0)(x-x_0)+o(x-x_0)
f(x)=f(x0)+f′(x0)(x−x0)+o(x−x0)它表明在点
x
0
x_0
x0附近,函数
f
(
x
)
f(x)
f(x)可以用
x
−
x
0
x-x_0
x−x0的线性函数近似表示,即有,当
∣
x
−
x
0
∣
|x-x_0|
∣x−x0∣很小时,
f
(
x
)
≈
f
(
x
0
)
+
f
′
(
x
0
)
(
x
−
x
0
)
f(x)\approx f(x_0)+f'(x_0)(x-x_0)
f(x)≈f(x0)+f′(x0)(x−x0)线性函数是多项式中幂次最低的一类,其特点是运算最简便,但是精度不高,而且无法具体估计误差的大小,所以当精度要求高且需要估计误差的时候,就要用高次多项式来近似表达函数,同时给出误差公式。
设函数
f
(
x
)
f(x)
f(x)在含
x
0
x_0
x0的开区间内具有直到
(
n
+
1
)
(n+1)
(n+1)阶导数,要找出一个关于
(
x
−
x
0
)
(x-x_0)
(x−x0)的
n
n
n次多项式
P
n
(
x
)
=
a
0
+
a
1
(
x
−
x
0
)
+
a
2
(
x
−
x
0
)
2
+
.
.
.
+
a
n
(
x
−
x
0
)
n
P_n(x)=a_0+a_1(x-x_0)+a_2(x-x_0)^2+...+a_n(x-x_0)^n
Pn(x)=a0+a1(x−x0)+a2(x−x0)2+...+an(x−x0)n来近似表达
f
(
x
)
f(x)
f(x),使
P
n
(
x
)
P_n(x)
Pn(x)与
f
(
x
)
f(x)
f(x)之差是比
(
x
−
x
0
)
(x-x_0)
(x−x0)高阶的无穷小。
(2)泰勒公式
用多项式 P n ( x ) P_n(x) Pn(x)近似表达函数 f ( x ) f(x) f(x),在几何上就是要求曲线 y = P n ( x ) y=P_n(x) y=Pn(x)与曲线 y = f ( x ) y=f(x) y=f(x)在点 x 0 x_0 x0附近有很高的密切程度,即要求它们在点 x 0 x_0 x0相交、具有公切线、有相同的弯曲程度和凹凸性,于是有 P n ( x 0 ) = f ( x 0 ) , P n ′ ( x 0 ) = f ′ ( x 0 ) , P n ′ ′ ( x 0 ) = f ′ ′ ( x 0 ) ; P_n(x_0)=f(x_0),\ P_n'(x_0)=f'(x_0),\ P_n''(x_0)=f''(x_0); Pn(x0)=f(x0), Pn′(x0)=f′(x0), Pn′′(x0)=f′′(x0);如果要求更多的密切程度,则还应有 P n ′ ′ ′ ( x 0 ) = f ′ ′ ′ ( x 0 ) , . . . , P n ( n ) ( x 0 ) = f ( n ) ( x 0 ) . P'''_n(x_0)=f'''(x_0),\ ...\ ,P_n^{(n)}(x_0)=f^{(n)}(x_0). Pn′′′(x0)=f′′′(x0), ... ,Pn(n)(x0)=f(n)(x0).由此可以确定出 P n ( x ) P_n(x) Pn(x)的各个系数为 a 0 = f ( x 0 ) , a 1 = f ′ ( x 0 ) , a 2 = 1 2 ! f ′ ′ ( x 0 ) , . . . , a n = 1 n ! f ( n ) ( x 0 ) a_0=f(x_0),\ a_1=f'(x_0),\ a_2=\frac{1}{2!}f''(x_0),\ ...,\ a_n=\frac{1}{n!}f^{(n)}(x_0) a0=f(x0), a1=f′(x0), a2=2!1f′′(x0), ..., an=n!1f(n)(x0)从而有 P n ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 + . . . , + f ( n ) ( x 0 ) n ! ( x − x 0 ) n . P_n(x)=f(x_0)+f'(x_0)(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2+...,+\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n. Pn(x)=f(x0)+f′(x0)(x−x0)+2!f′′(x0)(x−x0)2+...,+n!f(n)(x0)(x−x0)n.这样一来就可以满足
泰勒公式:
如果函数 f ( x ) f(x) f(x)在含有点 x 0 x_0 x0的某个开区间 ( a , b ) (a,b) (a,b)内具有直到 ( n + 1 ) (n+1) (n+1)阶导数,则当 x x x在 ( a , b ) (a,b) (a,b)内时,有 f ( x ) = f ( x 0 ) + f ′ ( x 0 ) ( x − x 0 ) + f ′ ′ ( x 0 ) 2 ! ( x − x 0 ) 2 + . . . , + f ( n ) ( x 0 ) n ! ( x − x 0 ) n + R n ( x ) , f(x)=f(x_0)+f'(x_0)(x-x_0)+\frac{f''(x_0)}{2!}(x-x_0)^2+...,+\frac{f^{(n)}(x_0)}{n!}(x-x_0)^n+R_n(x), f(x)=f(x0)+f′(x0)(x−x0)+2!f′′(x0)(x−x0)2+...,+n!f(n)(x0)(x−x0)n+Rn(x),其中 R n ( x ) = f ( n + 1 ) ( ξ ) ( n + 1 ) ! ( x − x 0 ) n + 1 R_n(x)=\frac{f^{(n+1)}(\xi)}{(n+1)!}(x-x_0)^{n+1} Rn(x)=(n+1)!f(n+1)(ξ)(x−x0)n+1这里 ξ \xi ξ是 x 0 x_0 x0与 x x x之间的某个值。
R n ( x ) R_n(x) Rn(x)是 n n n阶泰勒展开的拉格朗日型余项,在本文中不会用到,对此的介绍这里略过。
(3)泰勒展开
f ( x ) f(x) f(x)的泰勒公式去掉余项后无限项的累加,我们称为泰勒展开,即: f ( x ) = ∑ i = 0 ∞ f ( i ) ( x 0 ) i ! ( x − x 0 ) i f(x)=\sum _{i=0}^\infty \frac{f^{(i)}(x_0)}{i!}(x-x_0)^i f(x)=i=0∑∞i!f(i)(x0)(x−x0)i
(4)麦克劳林展开
对泰勒展开的 x 0 x_0 x0取 0 0 0,得到: f ( x ) = ∑ i = 0 ∞ f ( i ) ( 0 ) i ! x i f(x)=\sum _{i=0}^\infty \frac{f^{(i)}(0)}{i!}x^i f(x)=i=0∑∞i!f(i)(0)xi
3.欧拉公式
(1)定理
e i x = cos x + i sin x e^{ix}=\cos x+i\sin x eix=cosx+isinx
(2)证明
分别对 e i x , cos x , sin x e^{ix},\cos x,\sin x eix,cosx,sinx使用麦克劳林展开:
e i x = ∑ k = 0 ∞ ( i x ) k k ! = ∑ k m o d    2 = 0 ∞ ( − 1 ) k 2 x k k ! + i ∑ k m o d    2 = 1 ∞ ( − 1 ) k + 1 2 x k k ! e^{ix}=\sum_{k=0}^\infty \frac{(ix)^k}{k!}=\sum_{k\mod2=0}^\infty \frac{(-1)^\frac{k}{2}x^k}{k!}+i\sum_{k\mod2=1}^\infty \frac{(-1)^\frac{k+1}{2}x^k}{k!} eix=k=0∑∞k!(ix)k=kmod2=0∑∞k!(−1)2kxk+ikmod2=1∑∞k!(−1)2k+1xk
cos x = ∑ k m o d    2 = 0 ∞ ( − 1 ) k 2 cos ( 0 ) x k k ! + ∑ k m o d    2 = 1 ∞ ( − 1 ) k + 1 2 sin ( 0 ) x k k ! = ∑ k m o d    2 = 0 ∞ ( − 1 ) k 2 x k k ! \cos x=\sum_{k\mod2=0}^{\infty}\frac{(-1)^{\frac{k}{2}}\cos(0)x^k}{k!}+\sum_{k\mod2=1}^{\infty}\frac{(-1)^{\frac{k+1}{2}}\sin(0)x^k}{k!}=\sum_{k\mod2=0}^\infty \frac{(-1)^\frac{k}{2}x^k}{k!} cosx=kmod2=0∑∞k!(−1)2kcos(0)xk+kmod2=1∑∞k!(−1)2k+1sin(0)xk=kmod2=0∑∞k!(−1)2kxk
sin x = ∑ k m o d    2 = 0 ∞ ( − 1 ) k 2 sin ( 0 ) x k k ! + ∑ k m o d    2 = 1 ∞ ( − 1 ) k + 1 2 cos ( 0 ) x k k ! = ∑ k m o d    2 = 1 ∞ ( − 1 ) k + 1 2 x k k ! \sin x=\sum_{k\mod2=0}^{\infty}\frac{(-1)^{\frac{k}{2}}\sin(0)x^k}{k!}+\sum_{k\mod2=1}^{\infty}\frac{(-1)^{\frac{k+1}{2}}\cos(0)x^k}{k!}=\sum_{k\mod2=1}^\infty \frac{(-1)^\frac{k+1}{2}x^k}{k!} sinx=kmod2=0∑∞k!(−1)2ksin(0)xk+kmod2=1∑∞k!(−1)2k+1cos(0)xk=kmod2=1∑∞k!(−1)2k+1xk
得证。
(3)推论
e i π = − 1 e^{i\pi}=-1 eiπ=−1
4.单位根
(1)定义
设
n
n
n是正整数,当一个数的
n
n
n次乘方等于
1
1
1 时,称此数为
n
n
n次单位根。在复数范围内,
n
n
n次单位根有
n
n
n个。关于复平面1的知识见注释。
设
ω
n
k
\omega _n^k
ωnk为
n
n
n次单位根的第
k
k
k个,可以证明:
ω
n
k
=
e
2
π
k
i
n
\omega _n^k=e ^\frac{2\pi ki}{n}
ωnk=en2πki
证明: ( ω n k ) n = ( e 2 π k i n ) n = e 2 π k i = ( e π i ) 2 k = ( − 1 ) 2 k = 1 (\omega _n^k)^n=(e ^\frac{2\pi ki}{n})^n=e ^{2\pi ki}=(e ^{\pi i})^{2k}=(-1)^{2k}=1 (ωnk)n=(en2πki)n=e2πki=(eπi)2k=(−1)2k=1
(2)相关结论
结论1:
( cos θ + i sin θ ) x = cos ( x θ ) + i sin ( x θ ) (\cos \theta +i\sin \theta)^x=\cos(x\theta)+i\sin(x\theta) (cosθ+isinθ)x=cos(xθ)+isin(xθ)
证:当 x = 1 x=1 x=1时,结论显然成立,设当 x = k x=k x=k时,该式成立,则当 x = k + 1 x=k+1 x=k+1时: ( cos θ + i sin θ ) k + 1 = ( cos θ + i sin θ ) k ( cos θ + i sin θ ) (\cos \theta +i\sin \theta)^{k+1}=(\cos \theta +i\sin \theta)^k(\cos \theta +i\sin \theta) (cosθ+isinθ)k+1=(cosθ+isinθ)k(cosθ+isinθ) = ( cos k θ + i sin k θ ) ( cos θ + i sin θ ) =(\cos k\theta +i\sin k\theta)(\cos \theta +i\sin \theta) =(coskθ+isinkθ)(cosθ+isinθ) = cos k θ cos θ − sin k θ sin θ + i ( sin k θ cos k + cos k θ sin θ ) =\cos k\theta \cos \theta-\sin k\theta\sin \theta+i(\sin k\theta\cos k+\cos k\theta\sin \theta) =coskθcosθ−sinkθsinθ+i(sinkθcosk+coskθsinθ) = cos [ ( k + 1 ) θ ] + i sin [ ( k + 1 ) θ ] =\cos[(k+1)\theta]+i\sin[(k+1)\theta] =cos[(k+1)θ]+isin[(k+1)θ]所以对于所有的 x ≥ 1 , x ∈ Z x\ge1,x\in \Z x≥1,x∈Z,该式成立。
结论2:
ω 2 n 2 k = ω n k \omega_{2n}^{2k}=\omega_{n}^{k} ω2n2k=ωnk
证: ω 2 n 2 k = e 4 π k i 2 n = e 2 π k i n = ω n k \omega_{2n}^{2k}=e ^\frac{4\pi ki}{2n}=e ^\frac{2\pi ki}{n}=\omega_{n}^{k} ω2n2k=e2n4πki=en2πki=ωnk
结论3:
ω n a ω n b = ω n a + b \omega_{n}^{a}\omega_{n}^{b}=\omega_{n}^{a+b} ωnaωnb=ωna+b
证: ω n a ω n b = e 2 π a i n e 2 π b i n = e 2 π ( a + b ) i n = ω n a + b \omega_{n}^{a}\omega_{n}^{b}=e ^\frac{2\pi ai}{n}e ^\frac{2\pi bi}{n}=e ^\frac{2\pi (a+b)i}{n}=\omega_{n}^{a+b} ωnaωnb=en2πaien2πbi=en2π(a+b)i=ωna+b
结论4:
ω n k + n 2 = − ω n k \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k} ωnk+2n=−ωnk
证: ω n k + n 2 = cos ( 2 k π n + π ) + i sin ( 2 k π n + π ) = − cos ( 2 k π n ) − i sin ( 2 k π n ) = − ω n k \omega_{n}^{k+\frac{n}{2}}=\cos (\frac{2k\pi}{n}+\pi)+i\sin (\frac{2k\pi}{n}+\pi)=-\cos (\frac{2k\pi}{n})-i\sin (\frac{2k\pi}{n})=-\omega_{n}^{k} ωnk+2n=cos(n2kπ+π)+isin(n2kπ+π)=−cos(n2kπ)−isin(n2kπ)=−ωnk
*结论5:
( ω n k ) n = 1 (\omega_{n}^{k})^n=1 (ωnk)n=1
证: ( ω n k ) n = e 2 π k i = ( e π i ) 2 k = ( − 1 ) 2 k = 1 (\omega_{n}^{k})^n=e ^{2\pi ki}=(e ^{\pi i})^{2k}=(-1)^{2k}=1 (ωnk)n=e2πki=(eπi)2k=(−1)2k=1
二.离散傅里叶变换与离散傅里叶逆变换(DFT&IDFT)
1.离散傅里叶变换(DFT)
设 f ( x ) f(x) f(x)为 n − 1 n-1 n−1次多项式,该过程通过将 ω n 0 , ω n 1 , . . . , ω n n \omega_n^0,\omega_n^1,...,\omega_n^n ωn0,ωn1,...,ωnn带入 f ( x ) f(x) f(x),得到 f ( x ) f(x) f(x)的点值表示。点值 b i = f ( ω n i ) = ∑ j = 0 n − 1 a j ( ω n i ) j . b_i=f(\omega_n^i)=\sum_{j=0}^{n-1}a_j(\omega_n^i)^j. bi=f(ωni)=j=0∑n−1aj(ωni)j.
2.离散傅里叶逆变换(IDFT)
该过程将使用
DFT
\text{DFT}
DFT得到的
f
(
x
)
f(x)
f(x)的多项式点值表示,得到该多项式的系数表示。
具体的做法是将
b
i
b_i
bi当成一个多项式
g
(
x
)
g(x)
g(x)的系数表示,即:
g
(
x
)
=
b
0
+
b
1
x
+
b
2
x
2
+
.
.
.
+
b
n
−
1
x
n
−
1
,
g(x)=b_0+b_1x+b_2x^2+...+b_{n-1}x^{n-1},
g(x)=b0+b1x+b2x2+...+bn−1xn−1,那么将
ω
n
0
,
ω
n
−
1
,
.
.
.
,
ω
n
−
n
\omega_n^0,\omega_n^{-1},...,\omega_n^{-n}
ωn0,ωn−1,...,ωn−n带入
g
(
x
)
g(x)
g(x),得到
g
(
x
)
g(x)
g(x)的点值
c
k
=
g
(
ω
n
−
k
)
=
∑
i
=
0
n
−
1
b
i
(
ω
n
−
k
)
i
,
c_k=g(\omega_n^{-k})=\sum_{i=0}^{n-1}b_i(\omega_n^{-k})^i,
ck=g(ωn−k)=i=0∑n−1bi(ωn−k)i,可以证明
f
(
x
)
f(x)
f(x)的系数表示
a
k
=
c
k
n
a_k=\frac{c_k}{n}
ak=nck。
证明: c k = ∑ i = 0 n − 1 b i ( ω n − k ) i c_k=\sum_{i=0}^{n-1}b_i(\omega_n^{-k})^i ck=i=0∑n−1bi(ωn−k)i = ∑ i = 0 n − 1 ( ∑ j = 0 n − 1 a j ( ω n i ) j ) ( ω n − k ) i =\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega_n^i)^j)(\omega_n^{-k})^i =i=0∑n−1(j=0∑n−1aj(ωni)j)(ωn−k)i = ∑ i = 0 n − 1 ∑ j = 0 n − 1 a j ( ω n j − k ) i =\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j(\omega_n^{j-k})^i =i=0∑n−1j=0∑n−1aj(ωnj−k)i = ∑ j = 0 n − 1 a j ∑ i = 0 n − 1 ( ω n j − k ) i =\sum_{j=0}^{n-1}a_j\sum_{i=0}^{n-1}(\omega_n^{j-k})^i =j=0∑n−1aji=0∑n−1(ωnj−k)i1)当 j − k = 0 j-k=0 j−k=0时, ω n 0 = 1 \omega_n^0=1 ωn0=1, ∑ i = 0 n − 1 ( ω n 0 ) i = n . \sum_{i=0}^{n-1}(\omega_n^0)^i=n. ∑i=0n−1(ωn0)i=n.
2)当 j − k ≠ 0 j-k\ne0 j−k̸=0时,根据等比数列求和公式, ∑ i = 0 n − 1 ( ω n j − k ) i = ( ω n j − k ) n − 1 ω n j − k − 1 = 1 − 1 ω n j − k − 1 = 0. \sum_{i=0}^{n-1}(\omega_n^{j-k})^i=\frac{(\omega_n^{j-k})^n-1}{\omega_n^{j-k}-1}=\frac{1-1}{\omega_n^{j-k}-1}=0. ∑i=0n−1(ωnj−k)i=ωnj−k−1(ωnj−k)n−1=ωnj−k−11−1=0.
综上所述: c k = n a k . c_k=na_k. ck=nak.
所以我们只要把现有的点值表示用 ω n − k \omega_n^{-k} ωn−k带入到 DFT \text{DFT} DFT中即可得到 n a k na_k nak,除以 n n n后即可得到系数表示。
三.快速傅里叶变换(FFT)
1.分治加速
首先我们要把
n
n
n上调至
2
2
2的幂,后面本来没有的系数设置为
0
0
0就好了。
然后我们将
n
n
n个
n
n
n次单位根带入
f
(
x
)
f(x)
f(x)得:
f
(
ω
n
k
)
=
∑
j
=
0
n
−
1
a
j
(
ω
n
k
)
j
f(\omega_n^k)=\sum_{j=0}^{n-1}a_j(\omega_{n}^{k})^j
f(ωnk)=j=0∑n−1aj(ωnk)j设
f
(
x
)
f(x)
f(x)的奇数项为
g
(
x
)
g(x)
g(x),偶数项为
h
(
x
)
h(x)
h(x),令
m
=
n
2
m=\frac{n}{2}
m=2n我们分别拆出奇数项和偶数项得到:
∑
j
=
0
n
−
1
a
j
(
ω
n
k
)
j
=
∑
j
=
0
m
−
1
a
2
j
(
ω
n
k
)
2
j
+
ω
n
k
∑
j
=
0
m
−
1
a
2
j
+
1
(
ω
n
k
)
2
j
\sum_{j=0}^{n-1}a_j(\omega_{n}^{k})^j=\sum_{j=0}^{m-1}a_{2j}(\omega_{n}^{k})^{2j}+\omega_{n}^{k}\sum_{j=0}^{m-1}a_{2j+1}(\omega_{n}^{k})^{2j}
j=0∑n−1aj(ωnk)j=j=0∑m−1a2j(ωnk)2j+ωnkj=0∑m−1a2j+1(ωnk)2j
=
∑
j
=
0
m
−
1
a
2
j
(
ω
n
2
k
)
j
+
ω
n
k
∑
j
=
0
m
−
1
a
2
j
+
1
(
ω
n
2
k
)
j
=\sum_{j=0}^{m-1}a_{2j}(\omega_{n}^{2k})^{j}+\omega_{n}^{k}\sum_{j=0}^{m-1}a_{2j+1}(\omega_{n}^{2k})^{j}
=j=0∑m−1a2j(ωn2k)j+ωnkj=0∑m−1a2j+1(ωn2k)j
=
∑
j
=
0
m
−
1
a
2
j
(
ω
m
k
)
j
+
ω
n
k
∑
j
=
0
m
−
1
a
2
j
+
1
(
ω
m
k
)
j
=\sum_{j=0}^{m-1}a_{2j}(\omega_{m}^{k})^{j}+\omega_{n}^{k}\sum_{j=0}^{m-1}a_{2j+1}(\omega_{m}^{k})^{j}
=j=0∑m−1a2j(ωmk)j+ωnkj=0∑m−1a2j+1(ωmk)j
=
g
(
ω
m
k
)
+
ω
n
k
h
(
ω
m
k
)
=g(\omega_m^k)+\omega_{n}^{k}h(\omega_m^k)
=g(ωmk)+ωnkh(ωmk)如果我们已经求出所有
g
(
ω
m
k
)
,
h
(
ω
m
k
)
g(\omega_m^k),h(\omega_m^k)
g(ωmk),h(ωmk)的值,那么我们就能在
O
(
n
)
O(n)
O(n)的时间复杂度之内计算出所有
f
(
ω
n
k
)
f(\omega_{n}^{k})
f(ωnk)的值了,而求
g
(
ω
m
k
)
,
h
(
ω
m
k
)
g(\omega_m^k),h(\omega_m^k)
g(ωmk),h(ωmk)的值是完全相同的子问题。
由
ω
n
k
+
n
2
=
−
ω
n
k
\omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k}
ωnk+2n=−ωnk,可以推出
ω
n
n
+
k
=
ω
n
k
\omega_{n}^{n+k}=\omega_{n}^{k}
ωnn+k=ωnk,所以在程序实际实现的过程中,对于本层的
f
(
ω
n
k
)
f(\omega_{n}^{k})
f(ωnk)的值,我们只需要计算出
g
(
ω
m
k
)
,
h
(
ω
m
k
)
g(\omega_m^k),h(\omega_m^k)
g(ωmk),h(ωmk)的前
m
m
m项即可,即:
f
(
ω
n
k
)
=
{
g
(
ω
m
k
)
+
ω
n
k
h
(
ω
m
k
)
,
k
<
m
g
(
ω
m
k
−
m
)
−
ω
n
k
−
m
h
(
ω
m
k
−
m
)
,
m
≤
k
<
n
f(\omega_{n}^{k})= \begin{cases} g(\omega_m^k)+\omega_{n}^{k}h(\omega_m^k), & k<m\\ g(\omega_m^{k-m})-\omega_{n}^{k-m}h(\omega_m^{k-m}), & m\le k <n \end{cases}
f(ωnk)={g(ωmk)+ωnkh(ωmk),g(ωmk−m)−ωnk−mh(ωmk−m),k<mm≤k<n当
n
=
1
n=1
n=1时,
f
(
ω
1
0
)
=
a
0
f(\omega_{1}^{0})=a_0
f(ω10)=a0。
终上所述,我们通过递归的方法就可以实现FFT了。
- 代码
void fft(Com *a, int n, int inv){
if(n == 1) return;
int m = n/2;
//按奇偶分开
for(int i = 0; i < m; i ++) tmp[i] = a[2*i], tmp[i+m] = a[2*i+1];
for(int i = 0; i < n; i ++) a[i] = tmp[i];
//递归调用
fft(a, m, inv), fft(a+m, m, inv);
//减少三角函数的计算,提高精度和速度
Com x(1, 0), mi(cos(2*pi/n), sin(2*pi/n) * inv);
//合并奇偶项的值
for(int i = 0; i < m; i ++, x = x * mi)
tmp[i] = a[i] + x * a[i+m], tmp[i+m] = a[i] - x * a[i+m];
for(int i = 0; i < n; i ++) a[i] = tmp[i];
}
2.迭代加速
在分治加速的基础上还能通过将递归改为迭代来减小算法的常数。
考虑奇偶分类的过程,每次把二进制最后一位是
0
0
0的放在前面,
1
1
1的放在后面,然后在下一轮中去掉这个二进制位,这就相当于把所有的数按照前后颠倒的二进制进行排序。
那么一个数最后的位置就是把其二进制左右翻转后的数值大小。
设
R
x
R_x
Rx表示数字
x
x
x的二进制左右翻转后的值,我们按照从小到大的顺序求
R
x
R_x
Rx,当我们在求
R
x
R_x
Rx时
R
x
>
>
1
R_{x>>1}
Rx>>1的值一定已知,如果我们把
R
x
>
>
1
R_{x>>1}
Rx>>1的值右移一位,再把
x
x
x最低位补在最高位上,就可以得到
R
x
R_x
Rx了。
例如:
1010110
1
→
0
1010110
→
0110101
0
→
1
0110101
\color{red}1010110\color{blue}1\color{end}\to 0\color{red}1010110\color{end}\to \color{green}0110101\color{end}0\to\color{blue}1\color{green}0110101\color{end}
10101101→01010110→01101010→10110101红色部分的翻转值我们已经知道,所以只需要处理
x
x
x的最后一位就可以求出
R
x
R_x
Rx了。
for(int i = 0; i < n; i ++) R[i]= (R[i>>1] >> 1) | ((i&1)<<(BIT-1));
其中
BIT
\text{BIT}
BIT是
n
n
n的二进制中最高
1
1
1的位置。
这样我们就可以直接把
a
i
a_i
ai按
R
[
i
]
R[i]
R[i]的位置重新排放,通过依此对长度为
2
k
2^k
2k的数进行计算,来代替之前递归的过程。(这个过程完全可以类比zkw线段树)
- 代码
void fft(Com *a, int n, int inv){
if(R[n-1] != n-1) for(int i = 0; i < n; i ++) R[i] = (R[i>>1]>>1) | ((i&1)<<(BIT-1));
//如果所有的都交换会把已经换对的又换回去,所以只换小编号在前的。
for(int i = 0; i < n; i ++) if(i < R[i]) std::swap(a[i], a[R[i]]);
//枚举要计算的单个子函数的长度
for(int i = 1; i < n; i <<= 1){
Com mi(cos(pi/i), sin(pi/i) * inv);
//枚举一次操作开始的位置
for(int j = 0; j < n; j += (i<<1)){
Com x(1, 0);
//执行本次操作
for(int k = 0; k < i; k ++, x = x * mi){
Com t1 = a[j+k], t2 = x * a[j+k+i];
a[j+k] = t1 + t2, a[j+k+i] = t1 - t2;
}
}
}
}
四.快速数论变换(NTT)
由于我们希望可以在取模意义下也可以实现上面的过程,所以我们需要找到整数中模意义下的
ω
n
k
\omega_n^k
ωnk。
首先分析在上面的过程中用到的
ω
n
k
\omega_n^k
ωnk的性质:
- ω n 0 , . . . , ω n n − 1 \omega_n^0,...,\omega_n^{n-1} ωn0,...,ωnn−1互不相同。
- ω n 0 = ω n n = 1 \omega_n^0=\omega_n^{n}=1 ωn0=ωnn=1。
- ω n k + n 2 = − ω n k \omega_{n}^{k+\frac{n}{2}}=-\omega_{n}^{k} ωnk+2n=−ωnk, ω 2 n 2 k = ω n k \omega_{2n}^{2k}=\omega_{n}^{k} ω2n2k=ωnk
- ω n a ω n b = ω n a + b \omega_{n}^{a}\omega_{n}^{b}=\omega_{n}^{a+b} ωnaωnb=ωna+b
可以证明: ω n k = g k × ( P − 1 ) n \omega_n^k=g^{\frac{k\times (P-1)}{n}} ωnk=gnk×(P−1)其中:根据定义可知 P P P为质因数分解中 2 2 2的幂大于 n n n( n n n为 2 2 2的幂)的素数, g g g是 P P P的原根,定义为 g i ≠ g j ( i ≠ j ) m o d    P g^i\ne g^j(i\ne j)\mod P gi̸=gj(i̸=j)modP。
- 当 n ≤ P − 1 n\le P-1 n≤P−1时,根据定义可知 g n 0 , . . . , g n n − 1 g_n^0,...,g_n^{n-1} gn0,...,gnn−1互不相同,条件 1 1 1成立。
- 根据欧拉定理: g n n = g P − 1 = 1 g_n^n=g^{P-1}=1 gnn=gP−1=1,条件2成立。
- 由于 P P P是素数,并且 g n n ≡ 1 m o d    P g_n^n\equiv 1\mod P gnn≡1modP,这样 g n n 2 m o d    P g^{\frac{n}{2}}_n\mod P gn2nmodP必然是 1 1 1或 − 1 -1 −1,因为所有 g n k ( 0 ≤ k < n ) g^k_n(0\le k<n) gnk(0≤k<n)互不相同,所以 g n n 2 m o d    P = − 1 g^{\frac{n}{2}}_n\mod P=-1 gn2nmodP=−1,条件 3 3 3成立。
- 带入即可验证,条件 4 4 4成立。
根据以上条件可以说明:
- 在分治优化的时候, m = n 2 m=\frac{n}{2} m=2n只需要计算出 g ( ω m k ) , h ( ω m k ) g(\omega_m^k),h(\omega_m^k) g(ωmk),h(ωmk)的前 m m m项。
- 当 j − k = 0 j-k=0 j−k=0时, ω n 0 = 1 \omega_n^0=1 ωn0=1,当 j − k ≠ 0 j-k\ne0 j−k̸=0时,根据等比数列求和公式, ∑ i = 0 n − 1 ( ω n j − k ) i = ( ω n j − k ) n − 1 ω n j − k − 1 = 1 − 1 ω n j − k − 1 = 0 \sum_{i=0}^{n-1}(\omega_n^{j-k})^i=\frac{(\omega_n^{j-k})^n-1}{\omega_n^{j-k}-1}=\frac{1-1}{\omega_n^{j-k}-1}=0 ∑i=0n−1(ωnj−k)i=ωnj−k−1(ωnj−k)n−1=ωnj−k−11−1=0,所以快速数论逆变化方法与FFT相同。
- 综上所述:除改变单位根之外,只需要把FFT中的除法操作换为乘逆元,即可完成NTT。
- 常用的素数原根表
- 代码
void ntt(ll *a, int n, int inv){
if(R[n-1] != n-1) for(int i = 0; i < n; i ++) R[i] = (R[i>>1]>>1) | ((i&1) << (BIT-1));
for(int i = 0; i < n; i ++) if(i < R[i]) swap(a[i], a[R[i]]);
for(int i = 1; i < n; i <<= 1){
ll mi = inv == 1 ? pow(g, (mod-1)/(2*i)) : ni(pow(g, (mod-1)/(2*i)));
for(int j = 0; j < n; j += (i<<1)){
ll x = 1;
for(int k = 0; k < i; k ++, x = (x * mi) % mod){
ll t1 = a[j+k], t2 = (x * a[j+k+i]) % mod;
a[j+k] = (t1 + t2) % mod;
a[j+k+i] = (t1 - t2) % mod;
}
}
}
}
五.例题
0.模板题
例1:大数乘法 V2
给出
2
2
2个大整数
A
,
B
A,B
A,B,计算
A
×
B
A\times B
A×B的结果。(
A
,
B
A,B
A,B的长度
≤
100000
\le 100000
≤100000,
A
,
B
≥
0
A,B \ge 0
A,B≥0)
解析:
我们把一个整数当做一个多项式,当
f
(
x
=
10
)
f(x=10)
f(x=10)时即可把每个数都当成多项式的系数,这样求两个整数的和就变成了求两个多项式的卷积。
1.普通的卷积构造
例1:A+B Problem
给
n
n
n个数,
a
1
,
a
2
,
a
3
…
a
n
a_1,a_2,a_3…a_n
a1,a2,a3…an 问有多少组
i
,
j
,
k
i,j,k
i,j,k , 满足
a
[
i
]
+
a
[
j
]
=
a
[
k
]
a[i]+a[j]=a[k]
a[i]+a[j]=a[k] (
i
,
j
,
k
i,j,k
i,j,k互不相同)(
−
50000
≤
a
[
i
]
≤
50000
,
1
≤
N
≤
200000
-50000\le a[i]\le 50000 ,1\le N\le 200000
−50000≤a[i]≤50000,1≤N≤200000)
解析:
我们可以把所有的数
+
50000
+50000
+50000(记为
m
x
mx
mx),认为数字范围是
0
→
100000
0\to100000
0→100000,设一个数
x
x
x的数量是
n
u
m
[
x
]
num[x]
num[x],那么答案可以表示为:
a
n
s
=
∑
k
=
m
x
3
×
m
x
n
u
m
[
k
−
m
x
]
∑
i
+
j
=
k
n
u
m
[
i
]
n
u
m
[
j
]
,
i
≠
j
≠
k
−
m
x
ans=\sum_{k=mx}^{3\times mx}num[k- mx]\sum_{i+j=k}num[i]num[j], \ i\ne j\ne k-mx
ans=k=mx∑3×mxnum[k−mx]i+j=k∑num[i]num[j], i̸=j̸=k−mx其中
c
k
=
∑
i
+
j
=
k
n
u
m
[
i
]
n
u
m
[
j
]
c_k=\sum_{i+j=k}num[i]num[j]
ck=∑i+j=knum[i]num[j]可以用
FFT
\text{FFT}
FFT求得,在统计答案的时候要去掉
x
+
x
x+x
x+x的情况(即
i
=
j
i=j
i=j,会在
k
k
k是偶数的时候出现,位置为
k
2
\frac{k}{2}
2k),去掉
0
+
x
=
x
0+x=x
0+x=x和
x
+
0
=
x
x+0=x
x+0=x的情况(即减去
2
×
n
u
m
[
m
x
]
×
n
u
m
[
k
−
m
x
]
2\times num[mx]\times num[k-mx]
2×num[mx]×num[k−mx]),在处理
0
+
0
=
0
0+0=0
0+0=0时(即
k
=
2
×
m
x
k=2\times mx
k=2×mx)单独处理,相当于从
0
0
0的个数个数中取出
3
3
3个的排列数。
例2:Rock Paper Scissors
定义一种游戏,给定
5
5
5种牌,每种牌恰好赢两种其他不同的牌,同时输给两种不同的牌,输赢关系已经给出。分别给出他们的出牌序列,长度分别为
n
,
m
n,m
n,m,第二个人可以跳过第一个人的前
i
(
0
≤
i
<
n
)
i(0\le i<n)
i(0≤i<n)个出牌(第一个人的
i
+
1
i+1
i+1个与第二个人的第
1
1
1个开始),求第二个人最多赢多少次。(
1
≤
n
,
m
≤
1
0
6
1\le n,m\le10^6
1≤n,m≤106)
具体的:
R
→
S
,
L
∣
P
→
R
,
K
∣
S
→
P
,
L
∣
L
→
P
,
K
∣
K
→
R
,
S
R\to S,L\ |\ P\to R,K\ |\ S\to P,L\ |\ L\to P,K\ |\ K\to R,S
R→S,L ∣ P→R,K ∣ S→P,L ∣ L→P,K ∣ K→R,S其中
→
\to
→表示赢过。
输入样例:
12 4
RRRRRRRRRLLL
RRRS
解析:
枚举
5
5
5种符号:
将第二个串中这个符号设为
1
1
1,其他设为
0
0
0 ,然后倒序(倒序后正好对应位置相乘);
将第一个串中被这个符号打败的两个符号设为
1
1
1,其他设为
0
0
0。
定义结果数组
r
e
s
res
res的第
i
i
i个位置意味着从第二个人的
i
(
1
≤
i
≤
n
)
i(1\le i\le n)
i(1≤i≤n)位置开始,两个数组做
FFT
\text{FFT}
FFT以后结果数组为
c
c
c,则
r
e
s
[
i
]
=
c
[
i
+
m
−
2
]
res[i]=c[i+m-2]
res[i]=c[i+m−2](
c
c
c从
0
0
0开始)。
将五次
FFT
\text{FFT}
FFT的结果累加进
r
e
s
res
res中,取最大的即为答案。
如果一直重复使用数组做FFT,每一轮初始化的时候一定要把复数的实部和虚部一起初始化。
2.生成函数的构造与多项式的(分治/启发式)合并
例1:挑选队友
有
m
m
m个不同的盒子
n
n
n个不同球,输入第
i
i
i个盒子的球的个数
a
i
a_i
ai(至少有
1
1
1个),总共取
k
k
k个球,每个盒子至少取一个球(
k
≥
m
k\ge m
k≥m),求取出球的方案数(对
998244353
998244353
998244353取模)。
解析:
每个盒子构造一个多项式,形如
(
x
+
x
2
+
.
.
.
+
x
a
i
)
(x+x^2+...+x^{a_i})
(x+x2+...+xai),最后答案就是所有多项式乘积的
x
n
x^n
xn的系数。
可以考虑我们从每个多项式里面取出一项
x
x
x的幂代表选择的人数,最后总共选择了
n
n
n人,即
x
n
x^n
xn的系数。
在合并两个多项式的时候需要使用分治合并或启发式合并。
3.单位根循环性的利用
类似于平面直角坐标系的 x , y x,y x,y轴,复数平面(complex plane)是用水平的实轴与垂直的虚轴建立起来的复数的几何表示。一个复数的实部用沿着 x x x轴的位移表示,虚部用沿着 y y y轴的位移表示。
↩︎