其他分享
首页 > 其他分享> > 四大变换学习笔记(FFT,NTT,FMT,FWT)

四大变换学习笔记(FFT,NTT,FMT,FWT)

作者:互联网

FFT

介绍 FFT 之前,我们先介绍一下多项式。

多项式

多项式的概念

多项式是一个形如 \(A(x)=a_0+a_1x+a_2x^2+a_3x^3+\cdots+a_nx^n\) 的式子,我们称此时的 \(A(x)\) 为一个 \(n\) 次多项式,用更数学的语言来讲就是:

\[A(x)=\sum_{i=0}^na_ix^i \]

可以发现 \(A(x)\) 的本质是一个函数,所以我们也称 \(A(x)\) 为一个 \(n\) 次函数,即 \(n\) 次多项式函数,我们发现当 \(n\) 取 \(0\) 时该函数是一个常量,而当 \(n\) 取 \(1\) 或 \(2\) 时则是我们熟悉的一次函数和二次函数。

其中我们约定 \(A(x)_i\) 表示多项式函数 \(A(x)\) 的第 \(i\) 项,即上面的 \(a_i\),并且约定如果 \(i>n\),则 \(n\) 次多项式的第 \(i\) 项为 \(0\)。

多项式的四则运算

多项式的加法与减法非常简单,两个多项式的和差就是相对应的项相减的结果,即若 \(C(x)=A(x)\pm B(x)\),则:

\[C(x)_i=A(x)_i\pm B(x)_i \]

来看多项式的乘法,其实与我们初中对多项式的乘法处理相似,回忆一下:

\[\begin{aligned} &(a_0+a_1x+a_2x^2)(b_0+b_1x)\\ =&a_0b_0+a_0b_1x+a_1b_0x+a_1b_1x^2+a_2b_0x^2+a_2b_1x^3\\ =&a_0b_0+(a_0b_1+a_1b_0)x+(a_1b_1+a_2b_0)x^2+a_2b_1x^3 \end{aligned} \]

总结一下就是若 \(C(x)=A(x)\times B(x)\),则:

\[C(x)_i=\sum_{j=0}^iA(x)_jB(x)_{i-j} \]

我们也将多项式的乘法称为多项式卷积

多项式的除法涉及到多项式的乘法逆元,这里暂且跳过。

FFT

接下来讲 FFT,我们发现对于多项式加减法来说,时间复杂度都是 \(\mathcal O\left(n\right)\) 的,是一个比较优的复杂度,相对而言,多项式卷积的时间复杂度就偏劣,是 \(\mathcal O\left(n^2\right)\) 的,令人无法接受,考虑有没有更优的办法,其实是有的,这里引入一个点值表示法的概念。

初中时有用两点确定一条直线以及三点确定一条抛物线的情况,即用两点确定一个一次函数,三点确定一个二次函数,相对的我们想到可不可以用 \(n+1\) 个点确定一个 \(n\) 次函数呢?

这实际上是可以的,因为一个 \(n\) 次函数本质上就是由 \(n+1\) 个系数组成,所以相当于列了 \(n+1\) 个线性方程,所以用高斯消元是可以解的。

所以我们也可以用 \(n+1\) 个点来表示一个 \(n\) 次多项式,我们称这种用点来表示多项式的办法叫点值表示法,相对的我们将之前用 \(n+1\) 个系数表示的方法叫做系数表示法。

我们发现如果我们想要知道两个多项式的乘积,我们其实可以找出若干点,用两个多项式分别算出这些点对应的点值,然后将这两个点值乘起来,我们就得到了乘积多项式的点值表示,我们再把它转回系数表示就好了。

这样的话我们求乘积的时间复杂度就变成 \(\mathcal O\left(n\right)\) 的了,但是我们并没有什么好的办法将多项式从系数表示转为点值表示,暴力求解时间复杂度还是 \(\mathcal O\left(n^2\right)\) 的,得不偿失。

所以我们需要这种算法—— FFT。

FFT 能够在 \(\mathcal O\left(n\log n\right)\) 时间复杂度内将一个系数表示转为点值表示,是多项式转化的利器。

我们发现如果我们随意的选择我们求值的点,肯定是不行的,要考虑取一些点使它们满足某种优美的性质。

首先,我们约定我们选取的点值为 \(x_0\sim x_n\),令 \(\operatorname{FFT}(A)_i=A(x_i)=\sum_{j=0}^na_jx_i^j\)。

我们可以随意将 \(A(x)\) 的系数奇偶分开,即找到任意一个 \(m\ge\dfrac n2\),然后令:

\[\begin{aligned} &A_0(x)=\sum_{i=0}^mA(x)_{2i}x\\ &A_1(x)=\sum_{i=0}^mA(x)_{2i+1}x\\ \end{aligned} \]

那么前面的 \(\operatorname{FFT}(A)_i\) 就可以表示为:

\[\operatorname{FFT}(A)_i=A(x_i)=A_0(x_i^2)+A_1(x_i^2)x_i \]

发现每次如果我们将一个多项式分成两半,多项式次数规模就减少一半,如果我们把待求点值数也减半,因为我们发现上式的合并对于每个点值是 \(\mathcal O\left(1\right)\),也就是说总时间复杂度是 \(\mathcal O\left(\text{len}\right)\) 的,那么我们的总时间复杂度就可以控制在 \(\mathcal O\left(n\log n\right)\)。

我们发现点值从 \(A\) 到 \(A_0\) 和 \(A_1\) 时从 \(x\) 变成了 \(x^2\),也就是说如果我们找到一类数,它们的平方的数量只有原数量的一半,我们就成功了,第一个想法是相反数,但是取完一次就不行了,看来实数域没有这种东西,我们可以考虑从复数域找。

然后有一个东西惊人的与我们的目的吻合——单位根,我们可以先来认识一下这位救星。

单位根 \(\large\omega\)

再讲单位根之前得先讲一讲复数。

我们发现在实数域里面对负数开方是无解的,这究其原因都是因为 \(\sqrt{-1}\) 是未被定义的,因为任何一个满足 \(a>0\) 的 \(\sqrt{-a}\) 都可以写成 \(\sqrt a\cdot\sqrt{-1}\),所以我们可以强制规定 \(i=\sqrt{-1}\),我们一般称这个 \(i\) 为虚数单位,然后我们就可以将任何一个实数开方了。

我们可以将任意一个复数写成形如 \(a+bi\) 的形式,其中 \(a\) 叫实部,\(b\) 叫虚部,且因为这里的 \(a\),\(b\) 都是实数,所以实数的运算法则依然成立,所以复数的加减乘可以优美地定义如下:

\[(a+bi)\pm(c+di)=(a\pm c)+(b\pm d)i\\ (a+bi)(c+di)=ac+adi+bci+bdi^2=(ac-bd)+(ad+bc)i \]

然后我们再考虑如果要求一个复数 \(a+bi\) 的平方根 \(c+di\) ,我们可以解方程组:

\[\begin{cases} \begin{aligned} c^2-d^2&=a\\ 2cd&=b \end{aligned} \end{cases} \]

这个方程还是比较难解的,但是并不是解不出来,所以是可以再开根的。

我们之前发现一个事实——相反数的平方相等,其中的一个特例更为优美,就是 \(1\) 和 \(-1\),它们的平方都是 \(1\),仅仅是减少了一半的数,我们可以看成是在解 \(x^2=1\) 这样一个方程,它的解是 \(x_1=1,x_2=-1\),那么拓展延伸就可以得到在我们尝试将奇偶分离第二轮时我们就是在求一个方程 \(x^4=1\),然后发现在实数域的解还是只有 \(1\) 和 \(-1\),但是我们可以换一个角度思考,我们等于说是求 \((x^2)^2=1\),那么就是 \(x^2=\pm1\),也就是说我们还有两个根被舍了就是 \(x^2=-1\) 的两个根,就是虚数单位 \(i=\sqrt{-1}\),即 \(x_1=i,x_2=-i\),然后其实我们可以在虚数域上对这个数再开根,可以无限次数开下去,所以我们成功了。

然后发现这些数一定存在一个 \(n\) 满足它的 \(n\) 次方为 \(1\),所以我们称满足 \(\omega_n^n=1\) 的所有 \(\omega_n\) 为 \(n\) 次单位根

我们发现找高次单位根是一件很麻烦的事情,考虑有没有更简单的办法,就要引入复数的三角表示

数形结合一直是数学研究的利器,当我们研究实数时,我们利用数轴这一工具,并且以从原点出发的有向线段的长度表示一个数,相对的,我们发现其实每个虚数都可以表示成两个实数的组合,所以我们可以考虑将虚数表示在平面直角坐标系上,用 \(x\) 轴表示实部, \(y\) 轴表示虚部,然后用向量来表示一个复数,即用 \(\overrightarrow a=(a,b)\) 来表示复数 \(a+bi\),这样的话,称该向量与 \(x\) 轴的夹角为辐角 \(\theta\),可以得到 \(a+bi=|\overrightarrow a|(\cos\theta+\sin\theta i)\)。

这种表示法的其中一个优点是可以直接将复数加减转化为向量加减,并且乘法也非常简单。

令 \(a_1+b_1i\),\(a_2+b_2i\) 对应的向量分别为 \(\overrightarrow a_1\),\(\overrightarrow a_2\),辐角分别为 \(\theta_1\),\(\theta_2\),\((a_1+b_1i)(a_2+b_2i)\) 的乘积表示成:

\[\begin{aligned} &|\overrightarrow a_1||\overrightarrow a_2|(\cos\theta_1+\sin\theta_1i)(\cos\theta_2+\sin\theta_2i)\\ =&|\overrightarrow a_1||\overrightarrow a_2|((\cos\theta_1\cos\theta_2-\sin\theta_1\sin\theta_2)+(\cos\theta_1\sin\theta_2+\sin\theta_1\cos\theta_2)i)\\ =&|\overrightarrow a_1||\overrightarrow a_2|(\cos(\theta_1+\theta_2)+\sin(\theta_1+\theta_2)i) \end{aligned} \]

讲人话就是模长相乘,辐角相加。

我们发现这种方式可以很方便的求一个复数的高次幂,将对影响量的模长简写为 \(r\) ,则一个复数 \((r(\cos\theta+\sin\theta i))^k\) 为:

\[(r(\cos\theta+\sin\theta i))^k=r^k(\cos(k\theta)+\sin(k\theta)i) \]

这样我们就可以非常方便地求 \(\omega_n\),即 \(\omega_n=(\cos\dfrac{2k\pi}n+\sin\dfrac{2k\pi}ni),k\in\mathbb N\cap[0,n)\)。

然后就大成功,因为 \(\omega_n^2=\omega_\frac n2\),而且因为 \(n\) 次单位根有 \(n\) 个,所以肯定是每两个 \(\omega_n^2\) 对应一个 \(\omega_\frac n2\),且另一个性质是 \(\forall n>1,\ \omega_n^\frac n2=-1\),这个也非常显然。

DFT/IDFT

我们可以令 \(x_i=\omega_n^i\) ,再带回前面的 FFT 的式子中得到:

\[\begin{aligned} \operatorname{FFT}(A)_i&=A(\omega_n^i)\\ &=A_0(\omega_n^{2i})+A_1(\omega_n^{2i})\omega_n^i\\ &=A_0(\omega_\frac n2^i)+A_1(\omega_\frac n2^i)\omega_n^i \end{aligned} \]

然后我们惊奇的发现 \(\omega_\frac n2^i\) 数量就只有 \(\omega_n\) 的一半了,至此我们就成功的完成了被称为 DFT 的过程,就是从系数表示到点值表示的过程。

那么我们就已经离胜利只剩一步了,就是把点值表示转回系数表示。

标签:多项式,FMT,FFT,NTT,sin,theta,omega,我们
来源: https://www.cnblogs.com/LaoMang-no-blog/p/16148061.html