更 好 的 阅 读 体 验 \large更好的阅读体验 更好的阅读体验
原题链接
题目大意
给定一个
n
n
n 次多项式
F
(
x
)
F(x)
F(x),和一个
m
m
m 次多项式
G
(
x
)
G(x)
G(x)。
请求出
F
(
x
)
F(x)
F(x) 和
G
(
x
)
G(x)
G(x) 的卷积。
输入格式
第一行两个整数
n
,
m
n,m
n,m。
接下来一行
n
+
1
n+1
n+1 个数字,从低到高表示
F
(
x
)
F(x)
F(x) 的系数。
接下来一行
m
+
1
m+1
m+1 个数字,从低到高表示
G
(
x
)
G(x)
G(x) 的系数。
输出格式
一行 n + m + 1 n+m+1 n+m+1 个数字,从低到高表示 F ( x ) ⋅ G ( x ) F(x) \cdot G(x) F(x)⋅G(x) 的系数。
S a m p l e \mathbf{Sample} Sample I n p u t \mathbf{Input} Input
1 2
1 2
1 2 1
S a m p l e \mathbf{Sample} Sample O u t p u t \mathbf{Output} Output
1 4 5 2
H
i
n
t
&
E
x
p
l
a
i
n
\mathbf{Hint\&Explain}
Hint&Explain
样例如图所示。
其中粉色虚线为
F
(
x
)
F(x)
F(x),绿色虚线为
G
(
x
)
G(x)
G(x),蓝色实线为
F
(
x
)
⋅
G
(
x
)
F(x)\cdot G(x)
F(x)⋅G(x)。
数据范围
保证输入中的系数大于等于
0
0
0 且小于等于
9
9
9。
对于
100
%
100\%
100% 的数据,
1
≤
n
,
m
≤
10
6
1 \le n, m \leq {10}^6
1≤n,m≤106。
解题思路
温馨提示 此题解的字数有亿点点多,思维难度也会偏难,请读者 做好准备。 { \def\fcol{#FF9100} \def\scol{#FFF4E5} \def\sidelength{50pt} \def\titlehigh{30pt} \def\titlewordhigh{37pt} \color{\fcol}{\rule[0pt]{2pt}{\sidelength}} \color{\scol}{\rule[\titlehigh]{200pt}{20pt}} \kern{-200pt} \color{#FFFFFF}{\rule[0pt]{200pt}{\titlehigh}} \color{\fcol}{\raisebox{\titlewordhigh}{\kern{-195pt}{\footnotesize\bf 温馨提示}}} \color{black} % 在raisebox{ /here/ }{}修改文字高度,底线为7pt,依此减少10pt {\raisebox{17pt}{\kern{-195pt}{\footnotesize\bf 此题解的字数有亿点点多,思维难度也会偏难,请读者}}} {\raisebox{7pt}{\kern{-195pt}{\footnotesize\bf 做好准备。}}} } 温馨提示此题解的字数有亿点点多,思维难度也会偏难,请读者做好准备。
题目都告诉你了,是用FFT啊。
在介绍FFT之前,你需要知道一些前置的知识。
前置知识
1.
\texttt{1.}
1.复数
复数由两部分组成:实部和虚部。设
a
,
b
a,b
a,b 为实数,
i
=
−
1
i=\sqrt{-1}
i=−1,则形如
a
+
b
i
a+bi
a+bi 的数叫做复数。
2.
\texttt{2.}
2.复数的运算法则
设有两个复数为
a
+
b
i
a+bi
a+bi 和
c
+
d
i
c+di
c+di。
加法:
(
a
+
b
i
)
+
(
c
+
d
i
)
=
a
+
b
i
+
c
+
d
i
=
(
a
+
c
)
+
(
b
+
d
)
i
\begin{matrix} (a+bi)+(c+di) \\[5pt] =a+bi+c+di \\[5pt] =(a+c)+(b+d)i \end{matrix}
(a+bi)+(c+di)=a+bi+c+di=(a+c)+(b+d)i
其实就是把实部和虚部分别相加就可以了。
乘法:
(
a
+
b
i
)
(
c
+
d
i
)
=
a
c
+
a
d
i
+
b
c
i
+
b
d
i
2
=
a
c
+
(
a
d
+
b
c
)
i
−
b
d
s
=
(
a
c
−
b
d
)
+
(
a
d
+
b
c
)
i
\begin{matrix} (a+bi)(c+di) \\[5pt] =ac+adi+bci+bdi^2 \\[5pt] =ac+(ad+bc)i-bds \\[5pt] =(ac-bd)+(ad+bc)i \end{matrix}
(a+bi)(c+di)=ac+adi+bci+bdi2=ac+(ad+bc)i−bds=(ac−bd)+(ad+bc)i
几何定义:复数相乘,模长相乘,幅角相加。
Attention : \color{red}\texttt{Attention : } Attention : 以下内容默认 n n n 为 2 2 2 的正整数次幂。
3.
\texttt{3.}
3.单位根
在复平面(
x
x
x 轴代表实部,
y
y
y 轴代表虚部)上画一个单位圆(半径为
1
1
1 的圆),以圆点为起点,圆的
n
n
n 等分点为终点,做
n
n
n 个向量,设幅角为正且最小的向量对应的复数为
ω
n
\omega_n
ωn,称为
n
n
n 次单位根。下图所示的是
3
3
3 次单位根。
根据复数乘法的运算法则,不难推出剩下的
n
−
1
n-1
n−1 个单位根为
ω
n
2
,
ω
n
3
,
⋯
,
ω
n
n
\omega^2_n,\omega^3_n,\cdots,\omega^n_n
ωn2,ωn3,⋯,ωnn。
注意:
ω
n
0
=
ω
n
n
\omega^0_n=\omega^n_n
ωn0=ωnn。
幅角:假设以逆时针为正方向,从 x x x轴正半轴到已知向量的转角的有向角叫做幅角
4. \texttt{4.} 4.单位根的性质
- 由欧拉公式可得, ω n k = cos k 2 π n + i sin k 2 π n \omega^k_n=\cos k\frac{2\pi}{n}+i\sin k\frac{2\pi}{n} ωnk=coskn2π+isinkn2π。
-
∀
a
,
ω
a
n
a
k
=
ω
n
k
\forall a,\omega^{ak}_{an}=\omega^k_n
∀a,ωanak=ωnk
证明:
ω a n a k = cos a k ⋅ 2 π a n + i sin a k 2 π a n = cos k ⋅ 2 π n + i sin k 2 π n = ω n \begin{matrix} \omega^{ak}_{an}=\cos ak\cdot\frac{2\pi}{an}+i\sin ak\frac{2\pi}{an} \\[5pt] =\cos k\cdot\frac{2\pi}{n}+i\sin k\frac{2\pi}{n} \\[5pt] =\omega_n \end{matrix} ωanak=cosak⋅an2π+isinakan2π=cosk⋅n2π+isinkn2π=ωn -
ω
n
k
+
n
2
=
−
ω
n
k
\omega^{k+\frac{n}{2}}_{n}=-\omega^k_n
ωnk+2n=−ωnk
证明:
ω n k + n 2 = ω n k ⋅ ω n n 2 = ω n k ⋅ ( cos n 2 ⋅ 2 π n + i sin n 2 ⋅ 2 π n ) = ω n k ⋅ ( cos π + i sin π ) = ω n k ⋅ ( − 1 ) = − ω n k \begin{matrix} \omega^{k+\frac{n}{2}}_{n}=\omega^k_n\cdot\omega^{\frac{n}{2}}_n \\[5pt] =\omega^k_n\cdot\left(\cos\frac{n}{2}\cdot\frac{2\pi}{n}+i\sin\frac{n}{2}\cdot\frac{2\pi}{n}\right) \\[5pt] =\omega^k_n\cdot\left(\cos\pi+i\sin\pi\right) \\[5pt] =\omega^k_n\cdot(-1) \\[5pt] =-\omega^k_n \end{matrix} ωnk+2n=ωnk⋅ωn2n=ωnk⋅(cos2n⋅n2π+isin2n⋅n2π)=ωnk⋅(cosπ+isinπ)=ωnk⋅(−1)=−ωnk
接下来,就是重头戏FFT了。
快速傅里叶变换(FFT)
设有一个多项式
A
(
x
)
A(x)
A(x),他的系数为
(
a
0
,
a
1
,
a
2
,
⋯
,
a
n
−
1
)
(a_0,a_1,a_2,\cdots,a_{n-1})
(a0,a1,a2,⋯,an−1)。
那么
A
(
x
)
=
a
0
+
a
1
x
+
a
2
x
2
+
a
3
x
3
+
⋯
+
a
n
−
1
x
n
−
1
A(x)=a_0+a_1x+a_2x^2+a_3x^3+\cdots+a_{n-1}x^{n-1}
A(x)=a0+a1x+a2x2+a3x3+⋯+an−1xn−1
按照
x
x
x 的次数的奇偶性分类,有
A
(
x
)
=
(
a
0
+
a
2
x
2
+
⋯
+
a
n
−
2
x
n
−
2
)
+
(
a
1
x
+
a
3
x
3
+
⋯
+
a
n
−
1
x
n
−
1
)
A(x)=(a_0+a_2{x^2}+\cdots+a_{n-2}x^{n-2})+(a_1x+a_3{x^3}+\cdots+a_{n-1}x^{n-1})
A(x)=(a0+a2x2+⋯+an−2xn−2)+(a1x+a3x3+⋯+an−1xn−1)
设
A
1
(
x
)
=
a
0
+
a
2
x
+
⋯
+
a
n
−
2
x
n
2
−
1
A
2
(
x
)
=
a
1
+
a
3
x
+
⋯
+
a
n
−
1
x
n
2
−
1
A_1(x)=a_0+a_2x+\cdots+a_{n-2}x^{\frac n2-1} \\[5pt] A_2(x)=a_1+a_3x+\cdots+a_{n-1}x^{\frac n2-1}
A1(x)=a0+a2x+⋯+an−2x2n−1A2(x)=a1+a3x+⋯+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
(
k
<
n
2
)
\omega^k_n(k<\frac n2)
ωnk(k<2n) 代入原式,有
A
(
ω
n
k
)
=
A
1
(
ω
n
2
k
)
+
ω
n
k
A
2
(
ω
n
2
k
)
A(\omega^k_n)=A_1(\omega^{2k}_n)+\omega^k_nA_2(\omega^{2k}_n)
A(ωnk)=A1(ωn2k)+ωnkA2(ωn2k)
再把后半部分代进去。
将
ω
n
k
+
n
2
(
k
<
n
2
)
\omega^{k+\frac n2}_n(k<\frac n2)
ωnk+2n(k<2n) 代入原式,有
A
(
ω
n
k
+
n
2
)
=
A
1
(
ω
n
2
(
k
+
n
2
)
)
+
ω
n
k
+
n
2
A
2
(
ω
n
2
(
k
+
n
2
)
)
=
A
1
(
ω
n
2
k
+
n
)
−
ω
n
k
A
2
(
ω
n
2
k
+
n
)
=
A
1
(
ω
n
2
k
⋅
ω
n
n
)
−
ω
n
k
A
2
(
ω
n
2
k
⋅
ω
n
n
)
=
A
1
(
ω
n
2
k
)
−
ω
n
k
A
2
(
ω
n
2
k
)
A(\omega^{k+\frac n2}_n)=A_1(\omega^{2\left(k+\frac n2\right)}_n)+\omega^{k+\frac n2}_nA_2(\omega^{2(k+\frac n2)}_n) \\[5pt] =A_1(\omega^{2k+n}_n)-\omega^k_nA_2(\omega^{2k+n}_n) \\[5pt] =A_1(\omega^{2k}_n\cdot\omega^n_n)-\omega^k_nA_2(\omega^{2k}_n\cdot\omega^n_n) \\[5pt] =A_1(\omega^{2k}_n)-\omega^k_nA_2(\omega^{2k}_n)
A(ωnk+2n)=A1(ωn2(k+2n))+ωnk+2nA2(ωn2(k+2n))=A1(ωn2k+n)−ωnkA2(ωn2k+n)=A1(ωn2k⋅ωnn)−ωnkA2(ωn2k⋅ωnn)=A1(ωn2k)−ωnkA2(ωn2k)
发现没有,这两个式子只有一个常数项不同!
由于
k
k
k 在取遍
[
0
,
n
2
−
1
]
\left[0,\frac n2-1\right]
[0,2n−1] 的时候,
k
+
n
2
k+\frac n2
k+2n 取遍了
[
n
2
,
n
−
1
]
\left[\frac n2,n-1\right]
[2n,n−1]。所以,我们在求第一部分的时候,也可以同时求出第二部分的值。
直接把问题缩小了一半!
直接递归求解就好了。
时间复杂度:
Θ
(
n
log
2
n
)
\Theta(n\log_2n)
Θ(nlog2n)。
快速傅里叶逆变换(IFFT)
不要以为FFT就结束了。
刚才FFT是基于 点值表示法 的。
但题目里面说的是要 系数表示法 。所以要把点值表示法转换成系数表示法,这个过程就叫 傅里叶逆变换 。
设
(
y
0
,
y
1
,
⋯
,
y
n
−
1
)
(y_0,y_1,\cdots,y_{n-1})
(y0,y1,⋯,yn−1) 为
(
a
0
,
a
1
,
⋯
,
a
n
−
1
)
(a_0,a_1,\cdots,a_{n-1})
(a0,a1,⋯,an−1) 的傅里叶变换(就是点值表示法)。
又设
(
c
0
,
c
1
,
⋯
,
c
n
−
1
)
(c_0,c_1,\cdots,c_{n-1})
(c0,c1,⋯,cn−1) 为
(
y
0
,
y
1
,
⋯
,
y
n
−
1
)
(y_0,y_1,\cdots,y_{n-1})
(y0,y1,⋯,yn−1) 在
(
ω
n
0
,
ω
n
−
1
,
⋯
,
ω
n
−
(
n
−
1
)
)
(\omega^0_n,\omega^{-1}_n,\cdots,\omega^{-(n-1)}_n)
(ωn0,ωn−1,⋯,ωn−(n−1)) 的点值表示,即
c
k
=
∑
i
=
0
n
−
1
y
i
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
(
∑
j
=
0
n
−
1
a
j
(
ω
n
i
)
j
)
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
∑
j
=
0
n
−
1
a
j
(
ω
n
j
)
i
(
ω
n
−
k
)
i
=
∑
i
=
0
n
−
1
∑
j
=
0
n
−
1
a
j
(
ω
n
j
−
k
)
i
=
∑
i
=
0
n
−
1
a
j
(
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
)
c_k=\sum_{i=0}^{n-1}y_i(\omega^{-k}_n)^i \\[5pt] =\sum_{i=0}^{n-1}(\sum_{j=0}^{n-1}a_j(\omega^i_n)^j)(\omega^{-k}_n)^i \\[5pt] =\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j(\omega^j_n)^i(\omega^{-k}_n)^i \\[5pt] =\sum_{i=0}^{n-1}\sum_{j=0}^{n-1}a_j(\omega^{j-k}_n)^i \\[5pt] =\sum_{i=0}^{n-1}a_j(\sum_{j=0}^{n-1}(\omega^{j-k}_n)^i)
ck=i=0∑n−1yi(ωn−k)i=i=0∑n−1(j=0∑n−1aj(ωni)j)(ωn−k)i=i=0∑n−1j=0∑n−1aj(ωnj)i(ωn−k)i=i=0∑n−1j=0∑n−1aj(ωnj−k)i=i=0∑n−1aj(j=0∑n−1(ωnj−k)i)
先到这里,让我们换一个地方。
设
S
(
x
)
=
∑
i
=
0
n
−
1
x
i
S(x)=\sum\limits_{i=0}^{n-1}x^i
S(x)=i=0∑n−1xi。
当
k
≠
0
k\ne 0
k=0 时,把
ω
n
k
\omega^k_n
ωnk 代入得
S
(
ω
n
k
)
=
1
+
ω
n
k
+
(
ω
n
k
)
2
+
⋯
+
(
ω
n
k
)
n
−
1
(
1
)
S(\omega^k_n)=1+\omega^k_n+(\omega^k_n)^2+\cdots+(\omega^k_n)^{n-1} \kern{30pt}(1)
S(ωnk)=1+ωnk+(ωnk)2+⋯+(ωnk)n−1(1)
(
1
)
×
ω
n
k
(1)\times\omega^k_n
(1)×ωnk,得
ω
n
k
S
(
ω
n
k
)
=
ω
n
k
+
(
ω
n
k
)
2
+
⋯
+
(
ω
n
k
)
n
(
2
)
\omega^k_nS(\omega^k_n)=\omega^k_n+(\omega^k_n)^2+\cdots+(\omega^k_n)^n \kern{45pt}(2)
ωnkS(ωnk)=ωnk+(ωnk)2+⋯+(ωnk)n(2)
(
2
)
−
(
1
)
(2)-(1)
(2)−(1),得
ω
n
k
S
(
ω
n
k
)
−
S
(
ω
n
k
)
=
(
ω
n
k
)
n
−
1
(
ω
n
k
−
1
)
S
(
ω
n
k
)
=
(
ω
n
n
)
k
−
1
S
(
ω
n
k
)
=
(
ω
n
n
)
k
−
1
ω
n
k
−
1
S
(
ω
n
k
)
=
1
−
1
ω
n
k
−
1
S
(
ω
n
k
)
=
0
\omega^k_nS(\omega^k_n)-S(\omega^k_n)=(\omega^k_n)^n-1 \\[5pt] (\omega^k_n-1)S(\omega^k_n)=(\omega^n_n)^k-1 \\[5pt] S(\omega^k_n)=\frac{(\omega^n_n)^k-1}{\omega^k_n-1} \\[5pt] S(\omega^k_n)=\frac{1-1}{\omega^k_n-1} \\[5pt] S(\omega^k_n)=0
ωnkS(ωnk)−S(ωnk)=(ωnk)n−1(ωnk−1)S(ωnk)=(ωnn)k−1S(ωnk)=ωnk−1(ωnn)k−1S(ωnk)=ωnk−11−1S(ωnk)=0
所以,当
k
≠
0
k\ne 0
k=0 时,
S
(
ω
n
k
)
=
0
S(\omega^k_n)=0
S(ωnk)=0。
那当
k
=
0
k=0
k=0 时呢?
很显然,当
k
=
0
k=0
k=0 时,
S
(
ω
n
k
)
=
n
S(\omega^k_n)=n
S(ωnk)=n。因为此时
ω
n
k
=
1
\omega^k_n=1
ωnk=1,而又因为
1
n
=
1
,
n
∈
Z
1^n=1,n\in\Z
1n=1,n∈Z,而这样的项共有
n
n
n 项,所以整个式子的值为
n
×
1
=
n
n\times 1=n
n×1=n。
回到原式,继续考虑
c
k
=
∑
i
=
0
n
−
1
a
j
(
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
)
c_k=\sum_{i=0}^{n-1}a_j(\sum_{j=0}^{n-1}(\omega^{j-k}_n)^i)
ck=i=0∑n−1aj(j=0∑n−1(ωnj−k)i)
由上面的式子可以得出,当
j
≠
k
j\ne k
j=k 时,
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
=
0
\sum\limits_{j=0}^{n-1}(\omega^{j-k}_n)^i=0
j=0∑n−1(ωnj−k)i=0。当
j
=
k
j=k
j=k 时,
∑
j
=
0
n
−
1
(
ω
n
j
−
k
)
i
=
n
\sum\limits_{j=0}^{n-1}(\omega^{j-k}_n)^i=n
j=0∑n−1(ωnj−k)i=n。因此,
c
k
=
n
a
k
a
k
=
1
n
c
k
c_k=na_k \\[5pt] a_k=\frac1nc_k
ck=nakak=n1ck
由于
(
c
0
,
c
1
,
⋯
,
c
n
−
1
)
(c_0,c_1,\cdots,c_{n-1})
(c0,c1,⋯,cn−1) 为
(
y
0
,
y
1
,
⋯
,
y
n
−
1
)
(y_0,y_1,\cdots,y_{n-1})
(y0,y1,⋯,yn−1) 在
(
ω
n
0
,
ω
n
−
1
,
⋯
,
ω
n
−
(
n
−
1
)
)
(\omega^0_n,\omega^{-1}_n,\cdots,\omega^{-(n-1)}_n)
(ωn0,ωn−1,⋯,ωn−(n−1)) 的点值表示,所以相当于又做了一次FFT。
迭代实现
尽管你用了上面这么多字之精华写出来了一串代码,但是,你交到洛谷上面去,你还是会
WA
77pts
\color{E74C3C}\texttt{WA }\color{FADB14}\texttt{77pts}
WA 77pts。这时候就需要迭代来实现了。
盗用一下某位大佬的图
发现了吗?我们实际上依此操作的元素,实际上就是原来序列的下标的二进制反转得到的!
所以现在的难题就是:如何得到操作后的序列呢?
我们像,对于某个数
x
x
x 来说,他的原序列,是由
x
2
\frac x2
2x 左移一位再加上最后一位的特判得到的。那么在操作后的序列中也一样,他是由
x
2
\frac x2
2x 右移一位再加上最后一位(也就是第一位)的特判得到的。因此,我们就可以在
Θ
(
n
)
\Theta(n)
Θ(n) 的时间内求出
[
1
,
n
]
[1,n]
[1,n] 操作后的序列所对应的数了。这里给出转移公式:
设数
x
x
x 在
k
k
k 位二进制下操作后的序列所对应的数为
r
x
r_x
rx,有
r
x
=
⌊
r
x
2
2
⌋
+
[
x
m
o
d
2
=
1
]
⋅
2
k
−
1
r_x=\left\lfloor\frac{r_{\frac x2}}{2}\right\rfloor+[x\bmod 2=1]\cdot 2^{k-1}
rx=⌊2r2x⌋+[xmod2=1]⋅2k−1
其中
[
x
]
[x]
[x] 代表当
x
x
x 成立时
[
x
]
=
1
[x]=1
[x]=1,不成立时
[
x
]
=
0
[x]=0
[x]=0。
上代码
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
struct Complex{
Complex(double a=0.00,double b=0.00):real(a),imag(b){}
double real,imag;
};
const double pi=acos(-1.00);
Complex a[4000010],b[4000010];
int resort[4000010];
int n,m,lim,dig;
Complex operator + (Complex a,Complex b)
{
return Complex(a.real+b.real,a.imag+b.imag);
}
Complex operator - (Complex a,Complex b) {
return Complex(a.real-b.real,a.imag-b.imag);
}
Complex operator * (Complex a,Complex b)
{
double real,imag;
real=a.real*b.real-a.imag*b.imag;
imag=a.real*b.imag+a.imag*b.real;
return Complex(real,imag);
}
void FFT(Complex *c,int state)
{
for(int i=0; i<lim; i++)
if(i<resort[i])
swap(c[i],c[resort[i]]);
for(int i=1; i<lim; i<<=1)
{
Complex W1n(cos(pi/i),state*sin(pi/i));
for(int Size=i<<1,j=0; j<lim; j+=Size)
{
Complex W(1.00,0.00);
for(int k=0; k<i; k++,W=W*W1n)
{
Complex x=c[j+k],y=W*c[j+i+k];
c[j+k]=x+y;
c[j+i+k]=x-y;
}
}
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
/* Code */
cin>>n>>m;
for(int i=0; i<=n; i++)
cin>>a[i].real;
for(int i=0; i<=m; i++)
cin>>b[i].real;
lim=1,dig=0;
while(lim<=n+m)
lim<<=1,dig++;
for(int i=0; i<lim; i++)
resort[i]=(resort[i>>1]>>1)|((i&1)<<(dig-1));
FFT(a,1);
FFT(b,1);
for(int i=0; i<lim; i++)
a[i]=a[i]*b[i];
FFT(a,-1);
for(int i=0; i<=n+m; i++)
cout<<(int)(a[i].real/lim+0.5)<<" ";
return 0;
}
完美切题 ∼ \sim ∼