快速傅里叶变换与快速数论变换(FFT&NTT)学习笔记

快速傅里叶变换与快速数论变换(FFT&NTT)学习笔记

一.前置知识

1.复数
(1)定义式

我们定义 i 2 = − 1 i^2=-1 i2=1 a ∈ R , b ∈ R a\in \R,b\in \R aR,bR,则复数 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 z1z2=(a1+b1i)(a2+b2i)=(a1a2b1b2)+(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)(a2b2i)(a1+b1i)(a2b2i)=a22+b22(a1a2+b1b2)+(b1a2a1b2)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+...+an1xn1 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+...+bn1xn1
设多项式 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...an1为多项式 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,...,cn1,则: 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,...,xn1,带入到 f ( x ) f(x) f(x),得到 f ( x 0 ) , . . . , f ( x n − 1 ) f(x_0),...,f(x_{n-1}) f(x0),...,f(xn1)以后,可以通过 n n n个方程解出 a 0 , . . . , a n − 1 a_0,...,a_{n-1} a0,...,an1,所以我们也可以用 f ( x 0 ) , . . . , f ( x n − 1 ) f(x_0),...,f(x_{n-1}) f(x0),...,f(xn1)来表示一个多项式,称为该多项式的点值表示。
如果我们将 2 n − 1 2n-1 2n1个不同的 x 0 , . . . , x 2 n − 2 x_0,...,x_{2n-2} x0,...,x2n2数分别带入 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)(xx0)+o(xx0)它表明在点 x 0 x_0 x0附近,函数 f ( x ) f(x) f(x)可以用 x − x 0 x-x_0 xx0的线性函数近似表示,即有,当 ∣ x − x 0 ∣ |x-x_0| xx0很小时, 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)(xx0)线性函数是多项式中幂次最低的一类,其特点是运算最简便,但是精度不高,而且无法具体估计误差的大小,所以当精度要求高且需要估计误差的时候,就要用高次多项式来近似表达函数,同时给出误差公式。
设函数 f ( x ) f(x) f(x)在含 x 0 x_0 x0的开区间内具有直到 ( n + 1 ) (n+1) (n+1)阶导数,要找出一个关于 ( x − x 0 ) (x-x_0) (xx0) 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(xx0)+a2(xx0)2+...+an(xx0)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) (xx0)高阶的无穷小。

(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)(xx0)+2!f(x0)(xx0)2+...,+n!f(n)(x0)(xx0)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)(xx0)+2!f(x0)(xx0)2+...,+n!f(n)(x0)(xx0)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)(ξ)(xx0)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=0i!f(i)(x0)(xx0)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=0i!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=0k!(ix)k=kmod2=0k!(1)2kxk+ikmod2=1k!(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=0k!(1)2kcos(0)xk+kmod2=1k!(1)2k+1sin(0)xk=kmod2=0k!(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=0k!(1)2ksin(0)xk+kmod2=1k!(1)2k+1cos(0)xk=kmod2=1k!(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 x1,xZ,该式成立。

结论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 n1次多项式,该过程通过将 ω 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=0n1aj(ω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+...+bn1xn1,那么将 ω n 0 , ω n − 1 , . . . , ω n − n \omega_n^0,\omega_n^{-1},...,\omega_n^{-n} ωn0,ωn1,...,ωnn带入 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(ωnk)=i=0n1bi(ωnk)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=0n1bi(ωnk)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=0n1(j=0n1aj(ωni)j)(ωnk)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=0n1j=0n1aj(ωnjk)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=0n1aji=0n1(ωnjk)i1)当 j − k = 0 j-k=0 jk=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=0n1(ωn0)i=n.
2)当 j − k ≠ 0 j-k\ne0 jk̸=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=0n1(ωnjk)i=ωnjk1(ωnjk)n1=ωnjk111=0.
综上所述: c k = n a k . c_k=na_k. ck=nak.

所以我们只要把现有的点值表示用 ω n − k \omega_n^{-k} ωnk带入到 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=0n1aj(ω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=0n1aj(ωnk)j=j=0m1a2j(ωnk)2j+ωnkj=0m1a2j+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=0m1a2j(ωn2k)j+ωnkj=0m1a2j+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=0m1a2j(ωmk)j+ωnkj=0m1a2j+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 &lt; m g ( ω m k − m ) − ω n k − m h ( ω m k − m ) , m ≤ k &lt; n f(\omega_{n}^{k})= \begin{cases} g(\omega_m^k)+\omega_{n}^{k}h(\omega_m^k), &amp; k&lt;m\\ g(\omega_m^{k-m})-\omega_{n}^{k-m}h(\omega_m^{k-m}), &amp; m\le k &lt;n \end{cases} f(ωnk)={g(ωmk)+ωnkh(ωmk),g(ωmkm)ωnkmh(ωmkm),k<mmk<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 &gt; &gt; 1 R_{x&gt;&gt;1} Rx>>1的值一定已知,如果我们把 R x &gt; &gt; 1 R_{x&gt;&gt;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} 10101101010101100110101010110101红色部分的翻转值我们已经知道,所以只需要处理 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的性质:

  1. ω n 0 , . . . , ω n n − 1 \omega_n^0,...,\omega_n^{n-1} ωn0,...,ωnn1互不相同。
  2. ω n 0 = ω n n = 1 \omega_n^0=\omega_n^{n}=1 ωn0=ωnn=1
  3. ω 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
  4. ω 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×(P1)其中:根据定义可知 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 &ThinSpace;&ThinSpace; P g^i\ne g^j(i\ne j)\mod P gi̸=gj(i̸=j)modP

  1. n ≤ P − 1 n\le P-1 nP1时,根据定义可知 g n 0 , . . . , g n n − 1 g_n^0,...,g_n^{n-1} gn0,...,gnn1互不相同,条件 1 1 1成立。
  2. 根据欧拉定理: g n n = g P − 1 = 1 g_n^n=g^{P-1}=1 gnn=gP1=1,条件2成立。
  3. 由于 P P P是素数,并且 g n n ≡ 1 m o d &ThinSpace;&ThinSpace; P g_n^n\equiv 1\mod P gnn1modP,这样 g n n 2 m o d &ThinSpace;&ThinSpace; P g^{\frac{n}{2}}_n\mod P gn2nmodP必然是 1 1 1 − 1 -1 1,因为所有 g n k ( 0 ≤ k &lt; n ) g^k_n(0\le k&lt;n) gnk(0k<n)互不相同,所以 g n n 2 m o d &ThinSpace;&ThinSpace; P = − 1 g^{\frac{n}{2}}_n\mod P=-1 gn2nmodP=1,条件 3 3 3成立。
  4. 带入即可验证,条件 4 4 4成立。

根据以上条件可以说明:

  1. 在分治优化的时候, 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项。
  2. j − k = 0 j-k=0 jk=0时, ω n 0 = 1 \omega_n^0=1 ωn0=1,当 j − k ≠ 0 j-k\ne0 jk̸=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=0n1(ωnjk)i=ωnjk1(ωnjk)n1=ωnjk111=0,所以快速数论逆变化方法与FFT相同。
  3. 综上所述:除改变单位根之外,只需要把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,B0
解析:
我们把一个整数当做一个多项式,当 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,a3an 问有多少组 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 50000a[i]50000,1N200000
解析:
我们可以把所有的数 + 50000 +50000 +50000(记为 m x mx mx),认为数字范围是 0 → 100000 0\to100000 0100000,设一个数 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=mx3×mxnum[kmx]i+j=knum[i]num[j], i̸=j̸=kmx其中 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[kmx]),在处理 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 &lt; n ) i(0\le i&lt;n) i(0i<n)个出牌(第一个人的 i + 1 i+1 i+1个与第二个人的第 1 1 1个开始),求第二个人最多赢多少次。( 1 ≤ n , m ≤ 1 0 6 1\le n,m\le10^6 1n,m106
具体的: 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 RS,L  PR,K  SP,L  LP,K  KR,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(1in)位置开始,两个数组做 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+m2] 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 km),求取出球的方案数(对 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.单位根循环性的利用

  1. 类似于平面直角坐标系的 x , y x,y x,y轴,复数平面(complex plane)是用水平的实轴与垂直的虚轴建立起来的复数的几何表示。一个复数的实部用沿着 x x x轴的位移表示,虚部用沿着 y y y轴的位移表示。
    复平面 ↩︎

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值