本文共 8139 字,大约阅读时间需要 27 分钟。
多项式乘法
我们知道,多项式可以表示成:
A=∑i=0naixi A = ∑ i = 0 n a i x i 的形式。
对于两个多项式
A(x) A ( x ) 和
B(x) B ( x ) ,我们可以计算乘积
A⋅B A ⋅ B :
A⋅B=∑i=0sizeA∑j=0sizeBaibjxi+j A ⋅ B = ∑ i = 0 s i z e A ∑ j = 0 s i z e B a i b j x i + j 但是,这样算是
O(sizeA⋅sizeB) O ( s i z e A ⋅ s i z e B ) 的,太慢了,怎么办?
我们需要换一条思路。
首先,我们得知道一个东西:多项式的点值表示法。
我们把上面的称为
多项式的系数表示法,而点值表示法就是:
若
A A 多项式的次数为
n ,则任取
n n 个不相同的
x0,x1,⋯,xn ,求出
A A 多项式的
A(x0),A(x1),⋯,A(xn) 。记为:
<(x0,A(x0)),(x1,A(x1)),⋯,(xn,A(xn))> < ( x 0 , A ( x 0 ) ) , ( x 1 , A ( x 1 ) ) , ⋯ , ( x n , A ( x n ) ) > <script type="math/tex; mode=display" id="MathJax-Element-15"><(x_0,A(x_0)),(x_1,A(x_1)),\cdots,(x_n,A(x_n))></script>显然,一个有
n+1 n + 1 个点的点对唯一表示一个
n n 次多项式。
对于一个点值表示法下多项式
<(x0,A(x0)),(x1,A(x1)),⋯,(xn,A(xn))> 和
<(x0,B(x0)),(x1,B(x1)),⋯,(xn,B(xn))> < ( x 0 , B ( x 0 ) ) , ( x 1 , B ( x 1 ) ) , ⋯ , ( x n , B ( x n ) ) > <script type="math/tex; mode=display" id="MathJax-Element-19"><(x_0,B(x_0)),(x_1,B(x_1)),\cdots,(x_n,B(x_n))></script>它们的乘积是
<(x0,A(x0)⋅B(x0)),(x1,A(x1)⋅B(x1)),⋯,(xn,A(xn)⋅B(xn))> < ( x 0 , A ( x 0 ) ⋅ B ( x 0 ) ) , ( x 1 , A ( x 1 ) ⋅ B ( x 1 ) ) , ⋯ , ( x n , A ( x n ) ⋅ B ( x n ) ) > <script type="math/tex; mode=display" id="MathJax-Element-20"><(x_0,A(x_0)\cdot B(x_0)),(x_1,A(x_1)\cdot B(x_1)),\cdots,(x_n,A(x_n)\cdot B(x_n))></script>可以看出点值表示法的多项式相乘是
O(n) O ( n ) 的。
等等,我们好像找到了一个突破口!
为啥不把原来的系数表示法下的多项式转化成点值表示法呢?
仔细想一想:系数表示法与点值表示法互相转换,这个步骤好像是
O(n2) O ( n 2 ) 的。
而
FFT(快速傅里叶变换)就是为了优化这个
O(n2) O ( n 2 ) 。
PS:对于 O(n2) O ( n 2 ) 的点值表示法转化成系数表示法可以看。
FFT(快速傅里叶变换)
如果未特别说明,那么下面的多项式次数将是 2k−1 2 k − 1 的形式。
如果不是关键部分的公式或定理,不提供证明,自己出门右转百度。 首先介绍两个概念:
DFT(离散傅里叶变换)是将多项式由系数表示法转化成点值表示法;
IDFT(离散傅里叶逆变换)是将多项式由点值表示法转化成系数表示法;
而FFT就是上述两种变换的优化。
DFT部分
前置技能:
下面的内容将会提到
复数,不会的可以参考;
为了介绍FFT中的DFT部分,首先要介绍的是一个概念:单位根。
单位根:若有
此时将
z z 称为
n 次单位根。
若有
z∈R z ∈ R ,显然,
z z 可以等于
1 ,如果
n n 是偶数,则
z 还可以等于
−1 − 1 。
我们把范围扩大到
z∈C z ∈ C ,那么,我们可以得到
n n 个复数,它们将复平面上的单位圆等分成
n 份。
为了表示 n n 次单位根,我们引入一个公式。
欧拉公式:
ein=cosn+isinn 如果我们令:
ωn=e2πi/n ω n = e 2 π i / n 那么,
n n 次单位根就可以表示成
ω0n,ω1n,⋯,ωn−1n ,它们的
n n 次方显然都是
1 。
下面是关于 ωn ω n 的两条性质:(都是在 n n 为偶数的情况下)
ωn/2n=e(2πi/n)⋅(n/2)=eπi=cosπ+isinπ=−1(1)
ω2n=e2⋅2πi/n=ωn/2(2) (2) ω n 2 = e 2 ⋅ 2 π i / n = ω n / 2 下面,我们进入正题:DFT的求法。
在这里,我们令多项式次数为
n−1 n − 1 ,那么我们可以用点值表示成
<(ω0n,A(ω0n),(ω1n,A(ω1n)),⋯,(ωn−1n,A(ωn−1n))> < ( ω n 0 , A ( ω n 0 ) , ( ω n 1 , A ( ω n 1 ) ) , ⋯ , ( ω n n − 1 , A ( ω n n − 1 ) ) > <script type="math/tex; mode=display" id="MathJax-Element-50"><(\omega_n^0,A(\omega_n^0),(\omega_n^1,A(\omega_n^1)),\cdots,(\omega_{n}^{n-1},A(\omega_n^{n-1}))></script>
额……这时间复杂度好像并没有减少……
别急,我们来看
A(ωkn) A ( ω n k ) 能够表示成什么。
A(ωkn)====∑i=0n−1aiωkin∑i=0n/2−1a2iω2kin+∑i=0n/2−1a2i+1ω2ki+kn∑i=0n/2−1a2iωkin/2+∑i=0n/2−1a2i+1ωkin/2ωkn∑i=0n/2−1a2iωkin/2+ωn∑i=0n/2−1a2i+1ωkin/2(3)(4)(5)(6) (3) A ( ω n k ) = ∑ i = 0 n − 1 a i ω n k i (4) = ∑ i = 0 n / 2 − 1 a 2 i ω n 2 k i + ∑ i = 0 n / 2 − 1 a 2 i + 1 ω n 2 k i + k (5) = ∑ i = 0 n / 2 − 1 a 2 i ω n / 2 k i + ∑ i = 0 n / 2 − 1 a 2 i + 1 ω n / 2 k i ω n k (6) = ∑ i = 0 n / 2 − 1 a 2 i ω n / 2 k i + ω n ∑ i = 0 n / 2 − 1 a 2 i + 1 ω n / 2 k i 我们来分别看一看这神奇的步骤。
(3) ( 3 ) 步骤就是将
ωkn ω n k 带入原来的
A A 多项式。
(4) 步骤就是将原多项式拆成两个部分,按奇偶分类。
(5) ( 5 ) 步骤用到了上面提到的性质
(2) ( 2 ) 。
(6) ( 6 ) 步骤就是上面式子的后半部分提出公因数。
有了这个等式,我们就可以分治+递归解决DFT了。
算法步骤: - 对当前的多项式(一个数组)系数进行奇偶分类;
- 递归算出偶数部分的数组的 anse a n s e 和奇数部分的数组的 anso a n s o ;
- 这个多项式的 ans=anse+ωnanso a n s = a n s e + ω n a n s o
但是这个的常数好像很大啊?能不能减少一点呢?
上面的性质
(1) ( 1 ) 给了我们提示:
ωn/2+kn=ωn/2n⋅ωkn=−ωkn ω n n / 2 + k = ω n n / 2 ⋅ ω n k = − ω n k 在算 k<n2 k < n 2 时,可以顺便把 k≥n2 k ≥ n 2 的情况也算出来。
常数减小了一半!但是还是很大啊!
递归版的程序一般比非递归版慢,为啥不用
非递归版呢?
算法核心就是奇偶分类,分来分去最后分到了哪里?我们来研究研究。
显然,一个序列原来是
0,1,2,3,4,5,6,7 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,最终变成
0,4,2,6,1,5,3,7 0 , 4 , 2 , 6 , 1 , 5 , 3 , 7 。
把它们的二进制列出来:
000,001,010,011,100,101,110,111000,100,010,110,001,101,011,111 000 , 001 , 010 , 011 , 100 , 101 , 110 , 111 000 , 100 , 010 , 110 , 001 , 101 , 011 , 111 其中,上面是位置,下面是这个位置对应的数。
把上面的数翻转,好像就是下面的数!
没错,只需要计算一下每个数的二进制翻转后的结果,就能得到一个数最终对应的位置,也就能实现非递归版了。
代码:
int dft_fast(complex* ar,int len){ for(register int i=0; i >1); ++k)//枚举i次单位根的每一种取值 { complex x=ar[j+k],y=w*ar[j+k+(i>>1)]; ar[j+k]=x+y;//合并操作,将两边合并成一个点值表示法下的多项式 ar[j+k+(i>>1)]=x-y; w=w*wn; } } } return 0;}
IDFT部分
回顾上面的DFT部分,仔细思考一下,它本质就是在求:
⎧⎩⎨⎪⎪⎪⎪⎪⎪a0(ω0n)0+a1(ω0n)1+⋯+an−1(ω0n)n−1=A(ω0n)a0(ω1n)0+a1(ω1n)1+⋯+an−1(ω1n)n−1=A(ω1n)⋯a0(ωn−1n)0+a1(ωn−1n)1+⋯+an−1(ωn−1n)n−1=A(ωn−1n) { a 0 ( ω n 0 ) 0 + a 1 ( ω n 0 ) 1 + ⋯ + a n − 1 ( ω n 0 ) n − 1 = A ( ω n 0 ) a 0 ( ω n 1 ) 0 + a 1 ( ω n 1 ) 1 + ⋯ + a n − 1 ( ω n 1 ) n − 1 = A ( ω n 1 ) ⋯ a 0 ( ω n n − 1 ) 0 + a 1 ( ω n n − 1 ) 1 + ⋯ + a n − 1 ( ω n n − 1 ) n − 1 = A ( ω n n − 1 ) 其中,给定了 a0,a1,⋯,an−1 a 0 , a 1 , ⋯ , a n − 1 以及 ω0n,ω1n,⋯,ωn−1n ω n 0 , ω n 1 , ⋯ , ω n n − 1 ,求 A(ω0n),A(ω1n),⋯,A(ωn−1n) A ( ω n 0 ) , A ( ω n 1 ) , ⋯ , A ( ω n n − 1 ) 的值。
用矩阵表示如下:
⎡⎣⎢⎢⎢⎢⎢(ω0n)0(ω1n)0⋮(ωn−1n)0(ω0n)1(ω1n)1⋮(ωn−1n)1⋯⋯⋱⋯(ω0n)n−1(ω1n)n−1⋮(ωn−1n)n−1⎤⎦⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢a0a1⋮an−1⎤⎦⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢A(ω0n)A(ω1n)⋮A(ωn−1n)⎤⎦⎥⎥⎥⎥⎥(7) (7) [ ( ω 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 ) ] 我们令:
V=⎡⎣⎢⎢⎢⎢⎢(ω0n)0(ω1n)0⋮(ωn−1n)0(ω0n)1(ω1n)1⋮(ωn−1n)1⋯⋯⋱⋯(ω0n)n−1(ω1n)n−1⋮(ωn−1n)n−1⎤⎦⎥⎥⎥⎥⎥ V = [ ( ω 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 ] 那么IDFT的本质就是求 V V 矩阵的逆矩阵。
考虑下面这个矩阵:
D=⎡⎣⎢⎢⎢⎢⎢⎢(ω−0n)0(ω−1n)0⋮(ω−(n−1)n)0(ω−0n)1(ω−1n)1⋮(ω−(n−1)n)1⋯⋯⋱⋯(ω−0n)n−1(ω−1n)n−1⋮(ω−(n−1)n)n−1⎤⎦⎥⎥⎥⎥⎥⎥ 那么我们令 E=D⋅V E = D ⋅ V ,则:
Ei,j===∑k=0n−1Di,kVk,j∑k=0n−1(ω−in)k(ωkn)j∑k=0n−1ωk(j−i)n 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 ) 显然:
Ei,j={ 0(i≠j)n(i=j) E i , j = { 0 ( i ≠ j ) n ( i = j ) 因此,
1nD⋅V=1nE=In 1 n D ⋅ V = 1 n E = I n 。
(7) ( 7 ) 式两边同时左乘
1nD 1 n D ,可得
⎡⎣⎢⎢⎢⎢a0a1⋮an−1⎤⎦⎥⎥⎥⎥=1n⎡⎣⎢⎢⎢⎢⎢⎢(ω−0n)0(ω−1n)0⋮(ω−(n−1)n)0(ω−0n)1(ω−1n)1⋮(ω−(n−1)n)1⋯⋯⋱⋯(ω−0n)n−1(ω−1n)n−1⋮(ω−(n−1)n)n−1⎤⎦⎥⎥⎥⎥⎥⎥⎡⎣⎢⎢⎢⎢⎢A(ω0n)A(ω1n)⋮A(ωn−1n)⎤⎦⎥⎥⎥⎥⎥ [ 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 − ( n − 1 ) ) 0 ( ω n − ( n − 1 ) ) 1 ⋯ ( ω n − ( n − 1 ) ) n − 1 ] [ A ( ω n 0 ) A ( ω n 1 ) ⋮ A ( ω n n − 1 ) ] 这就相当于把DFT中 ωkn ω n k 都换成 ω−kn ω n − k 。
FFT总代码
int fft(complex* ar,int len,int op){ for(register int i=0; i >1); ++k) { complex x=ar[j+k],y=w*ar[j+k+(i>>1)]; ar[j+k]=x+y; ar[j+k+(i>>1)]=x-y; w=w*wn; } } } if(op==-1) { for(register int i=0; i
多项式乘法模板
#include #include #include const int maxn=100000;const double pi=acos(-1);struct complex{ double r,i; complex(double r_=0,double i_=0) { r=r_; i=i_; } complex operator +(const complex &other) { return complex(r+other.r,i+other.i); } complex operator -(const complex &other) { return complex(r-other.r,i-other.i); } complex operator *(const complex &other) { return complex(r*other.r-i*other.i,r*other.i+i*other.r); }};complex a[maxn<<2],b[maxn<<2],c[maxn<<2];int rev[maxn<<2],n,m;int fft(complex* ar,int len,int op){ for(register int i=0; i >1); ++k) { complex x=ar[j+k],y=w*ar[j+k+(i>>1)]; ar[j+k]=x+y; ar[j+k+(i>>1)]=x-y; w=w*wn; } } } if(op==-1) { for(register int i=0; i >1]>>1)|((i&1)<<(l-1)); } fft(a,m,1); fft(b,m,1); for(register int i=0; i