从多项式乘法到FFT
这一段大部分是复制以前我写的这篇博客:
https://blog.csdn.net/wang3312362136/article/details/79510933
这篇博客有详细的代码,但是阅读体验布星,因此我把它的大部分证明过程复制下来。
如果会fft,请跳过这一段。
多项式乘法
我们知道,多项式可以表示成:
A=∑i=0naixi
的形式。
对于两个多项式A(x)和B(x),我们可以计算乘积A⋅B:
A⋅B=∑i=0|A|∑j=0|B|aibjxi+j
但是,这样算是O(|A|×|B|)的,太慢了,怎么办?
我们需要换一条思路。
首先,我们得知道一个东西:多项式的点值表示法。
我们把上面的称为多项式的系数表示法,而点值表示法就是:
若A多项式的次数为n,则任取n个不相同的x0,x1,⋯,xn,求出A多项式的A(x0),A(x1),⋯,A(xn)。记为:
<(x0,A(x0)),(x1,A(x1)),⋯,(xn,A(xn))>
显然,一个有n+1个点的点对唯一表示一个n次多项式。
对于一个点值表示法下多项式
<(x0,A(x0)),(x1,A(x1)),⋯,(xn,A(xn))>
和
<(x0,B(x0)),(x1,B(x1)),⋯,(xn,B(xn))>
它们的乘积是
<(x0,A(x0)⋅B(x0)),(x1,A(x1)⋅B(x1)),⋯,(xn,A(xn)⋅B(xn))>
可以看出点值表示法的多项式相乘是O(n)的。
等等,我们好像找到了一个突破口!
为啥不把原来的系数表示法下的多项式转化成点值表示法呢?
仔细想一想:系数表示法与点值表示法互相转换,这个步骤好像是O(n2)的。
而FFT(快速傅里叶变换)就是为了优化这个O(n2)。
PS:对于O(n2)的点值表示法转化成系数表示法可以看百度百科中关于插值法的介绍。
FFT(快速傅里叶变换)
如果未特别说明,那么下面的多项式次数将是2k−1的形式。
如果不是关键部分的公式或定理,不提供证明,自己出门右转百度。
首先介绍两个概念:
DFT(离散傅里叶变换)是将多项式由系数表示法转化成点值表示法;
IDFT(离散傅里叶逆变换)是将多项式由点值表示法转化成系数表示法;
而FFT就是上述两种变换的优化。
DFT部分
前置技能:
下面的内容将会提到复数,不会的可以参考百度百科中关于复数的介绍;
为了介绍FFT中的DFT部分,首先要介绍的是一个概念:单位根。
单位根:若有
zn=1
此时将
z称为
n次单位根。
若有z∈ℝ,显然,z可以等于1,如果n是偶数,则z还可以等于−1。
我们把范围扩大到z∈ℂ,那么,我们可以得到n个复数,它们将复平面上的单位圆等分成n份。
为了表示n次单位根,我们引入一个公式。
欧拉公式:
ein=cosn+isinn
如果我们令:
ωn=e2πi/n
那么,n次单位根就可以表示成ω0n,ω1n,⋯,ωn−1n,它们的n次方显然都是1。
下面是关于ωn的两条性质:(都是在n为偶数的情况下)
ωn/2n=e(2πi/n)⋅(n/2)=eπi=cosπ+isinπ=−1(1)
ω2n=e2⋅2πi/n=ωn/2(2)
下面,我们进入正题:DFT的求法。
在这里,我们令多项式次数为n−1,那么我们可以用点值表示成
<(ω0n,A(ω0n),(ω1n,A(ω1n)),⋯,(ωn−1n,A(ωn−1n))>
额……这时间复杂度好像并没有减少……
别急,我们来看A(ωkn)能够表示成什么。
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)步骤就是将ωkn带入原来的A多项式。
(4)步骤就是将原多项式拆成两个部分,按奇偶分类。
(5)步骤用到了上面提到的性质(2)。
(6)步骤就是上面式子的后半部分提出公因数。
有了这个等式,我们就可以分治+递归解决DFT了。
算法步骤:
- 对当前的多项式(一个数组)系数进行奇偶分类;
- 递归算出偶数部分的数组的anse和奇数部分的数组的anso;
- 这个多项式的ans=anse+ωnanso
但是这个的常数好像很大啊?能不能减少一点呢?
上面的性质(1)给了我们提示:
ωn/2+kn=ωn/2n⋅ωkn=−ωkn
在算k<n2时,可以顺便把k≥n2的情况也算出来。
常数减小了一半!但是还是很大啊!
递归版的程序一般比非递归版慢,为啥不用非递归版呢?
算法核心就是奇偶分类,分来分去最后分到了哪里?我们来研究研究。
显然,一个序列原来是0,1,2,3,4,5,6,7,最终变成0,4,2,6,1,5,3,7。
把它们的二进制列出来:
000,001,010,011,100,101,110,111000,100,010,110,001,101,011,111
其中,上面是位置,下面是这个位置对应的数。
把上面的数翻转,好像就是下面的数!
没错,只需要计算一下每个数的二进制翻转后的结果,就能得到一个数最终对应的位置,也就能实现非递归版了。
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)
其中,给定了a0,a1,⋯,an−1以及ω0n,ω1n,⋯,ωn−1n,求A(ω0n),A(ω1n),⋯,A(ωn−1n)的值。
用矩阵表示如下:
⎡⎣⎢⎢⎢⎢(ω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)
我们令:
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⎤⎦⎥⎥⎥⎥
那么IDFT的本质就是求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,则:
Ei,j===∑k=0n−1Di,kVk,j∑k=0n−1(ω−in)k(ωkn)j∑k=0n−1ωk(j−i)n
显然:
Ei,j={0(i≠j)n(i=j)
因此
1nD⋅V=1nE=In
(7)式两边同时左乘1nD,可得
⎡⎣⎢⎢⎢⎢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)⎤⎦⎥⎥⎥⎥
这就相当于把DFT中ωkn都换成ω−kn。
IDFT过程中的小技巧
如果IDFT中逆矩阵使用的是V(使用的是DFT的过程),要得到IDFT的正确结果只需要执行一遍std::reverse(a+1,a+n)
,这是很容易证明的。
NTT(快速数论变换)
FFT要求采用复数和实数运算,如果数很大很容易产生精度误差。如果是模意义下呢?
前置技能
FFT,初级数论内容,原根。
一点微小的变化?
考虑原根的性质。
性质(1) 循环:gn+xn=gxn(modn)
性质(2) 折半:gxn=gx/2n/2(modm)
性质(3) ∑n−1i=0gin=0(modm)
可以发现原根的性质和单位根的性质差不多,因此可以用来做模意义下的FFT,就是NTT。
或者说,NTT就是把FFT的ω替换成了g。
由于使用的是gm−1i,其中m为模数,i为2k形式,因此模数是x⋅2k+1的形式(NTT模数)。
常见的NTT模数:
998244353,原根是3
1004535809,原根也是3
FWT(快速沃尔什变换)
上面求的都是多项式卷积
F(n)=∑i=0na(i)b(n−i)
或者换种方式
F(n)=∑i,j,i+j=na(i)b(j)
但是如果是下面的方式
F(n)=∑i,j,i∘j=na(i)b(j)
其中
∘是种位运算呢?
依然考虑转化成点值表达式再相乘。类比FFT,考虑用一种变换tf和逆变换utf,只要tf(A×B)=tf(A)×tf(B),那么就可以得到A×B=utf(tf(A)×tf(B))
下面我们分类讨论∘的位运算种类及tf,utf的种类。
集合交卷积(and)
![graph_and](https://img-blog.csdn.net/20180814214233618?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
集合并卷积(or)
![graph_or](https://img-blog.csdn.net/20180814214326407?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
集合对称差卷积(xor)
![graph_xor](https://img-blog.csdn.net/20180814214417256?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
子集卷积
![graph_subset_1](https://img-blog.csdn.net/20180814214511593?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![graph_subset_2](https://img-blog.csdn.net/20180814214534899?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
其他的方式
![wzy-1](https://img-blog.csdn.net/2018081513130781?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
![wzy-2](https://img-blog.csdn.net/20180815131322707?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dhbmczMzEyMzYyMTM2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
多项式求逆
前置技能
多项式乘法(FFT/NTT)
多项式的度
一个多项式的度就是这个多项式最高此项的次数,例如4x2+3x+1的度为2。
多项式的逆元
现在有两个多项式F,G,如果
F×G=1(modxn)(1)
并且
G的度小于等于
F的度,那么
G称为
F在模
xn意义下的逆元。
怎么求?假设已经求出了F在模x⌈n/2⌉下的逆元,为G′。
由定义得到
F×G′=1(modx⌈n/2⌉)(2)
将(1)放到模x⌈n/2⌉意义下
F×G=1(modx⌈n/2⌉)(3)
(2)−(3)得到
F×G′−F×G=0(modx⌈n/2⌉)
两边平方,得到
F2×G2−2×F2×G′×G+F2×G′2=0(modxn)
同时除以
F并处理
modxn意义下为
1的项
G=2G′−FG′2(modxn)
就是说,只要求出了模
x⌈n/2⌉意义下的逆元,就可以求出模
xn意义下的逆元。