快速傅里叶变换(FFT)

多项式的概念

1. 多项式次数界:

关于x的多项式即形如 f ( x ) = a 0 + a 1 x + a 2 x 1 + ⋯ + a n x n f(x)=a_0+a_1x+a_2x^1+\dots+a_nx^n f(x)=a0+a1x+a2x1++anxn的式子,其中最高项的次数为 n n n,则任何大于 n n n的整数都成为该多项式的次数界,但一般我们取最小的次数界,即该多项式的次数界为 n + 1 n+1 n+1。所以一个次数界为 n n n的多项式,它的最高项次数为 n − 1 n-1 n1.

2.多项式的两种表示方法:

系数表示法
一个次数界为n的多项式,最多有n个系数,如果确定了这n个系数,则多项式可以唯一确定。
点值表示法
如果给出了 n n n不同的的 x x x值,并对应的给出了 n n n f ( x ) f(x) f(x)值,则次数界为 n n n的多项式也可以唯一确定,所以我们也可以用 n n n ( x , f ( x ) ) (x,f(x)) (x,f(x))的点值,唯一地确定多项式。

3.多项式的运算

若有 f ( x ) = a 0 + a 1 x + a 2 x 2 + ⋯ + a n x n f(x)=a_0+a_1x+a_2x^2+\dots+a_nx^n f(x)=a0+a1x+a2x2++anxn, g ( x ) = b 0 + b 1 x + b 2 x 2 + ⋯ + b n x n g(x)=b_0+b_1x+b_2x^2+\dots+b_nx^n g(x)=b0+b1x+b2x2++bnxn,我们来讨论一下多项式的运算。
多项式的加减
f ( x ) ± g ( x ) = ( a 0 ± b 0 ) + ( a 1 ± b 1 ) x + ( a 2 ± b 2 ) x 2 + ⋯ + ( a n ± b n ) x n f(x)\pm g(x)=(a_0 \pm b_0)+(a_1\pm b_1)x+(a_2 \pm b_2)x^2+\dots+(a_n\pm b_n)x^n f(x)±g(x)=(a0±b0)+(a1±b1)x+(a2±b2)x2++(an±bn)xn.
一次加减操作是O(n)的。
而如果是点值表示法,我们需要对n个点值进行加减,所以也是 O ( n ) O(n) O(n)的。
多项式的乘法
如果是系数表示法,很明显是 O ( n 2 ) O(n^2) O(n2)的。
如果是点值表示法,因为最高项变成了2n-2次,所以我们需要2n-1对x和f(x)值才能唯一确定。这一过程仍然是 O ( n ) O(n) O(n)的。
对于两个多项式的系数表示的乘法运算,我们能否先把他们转换为点值表示法,然后进行乘法运算,然后再把结果转换为系数表示法呢?
我们先来看一下系数表示法和点值表示法互换的时间复杂度。
已知系数表示法,求一个点的f(x),可以使用秦九韶算法,在O(n)时间内求出来。
f ( x 0 ) = a 0 + x ( a 1 + x ( a 2 + x ( a 3 + x ( ⋯ + x ( a n − 1 + x a n ) ) …   ) f(x_0)=a_0+x(a_1+x(a_2+x(a_3+x(\dots+x(a_{n-1}+xa_n))\dots) f(x0)=a0+x(a1+x(a2+x(a3+x(+x(an1+xan)))
于是将一个多项式从系数表示法换成点值表示法需要 O ( n 2 ) O(n^2) O(n2).
那么反过来,将一个多项式从点值表示法换成系数表示法呢?
似乎更高!使用高斯消元,需要 O ( n 3 ) O(n^3) O(n3)的时间复杂度。
有没有什么办法优化?
忘了介绍了,从系数表示法转换为点值表示法,我们称这一过程为求值;从点值表示法换成系数表示法,这一过程我们称之为插值
使用拉格朗日插值法,我们可以将插值的时间复杂度稍微降低一点,
f ( x ) = ∑ 1 ≤ i ≤ n y i ∏ 1 ≤ j ≤ n ∧ j ≠ i x − x j x i − x j f(x)=\sum_{1 \leq i \leq n} y_i\prod_{1 \leq j \leq n \land j \neq i} \frac{x-x_j}{x_i-x_j} f(x)=1inyi1jnj̸=ixixjxxj
考虑到有很多重复的计算,看分子,我们可以预处理出 ω ( x ) = ∏ 1 ≤ j ≤ n ( x − x j ) \omega(x)=\prod_{1\leq j \leq n}(x-x_j) ω(x)=1jn(xxj)

l i = ∏ 1 ≤ j ≤ n ∧ j ≠ i y i x i − x j l_i=\prod_{1\leq j \leq n \land j \neq i}\frac{y_i}{x_i-x_j} li=1jnj̸=ixixjyi
则可得:
f ( x ) = ω ( x ) ∑ 1 ≤ i ≤ n l i x − x i f(x)=\omega(x)\sum_{1\leq i \leq n} \frac{l_i}{x-x_i} f(x)=ω(x)1inxxili
时间复杂度降为 O ( n 2 ) O(n^2) O(n2)
然而时间复杂度还是没法优化,仍然为 O ( n 2 ) O(n^2) O(n2).

4.快速傅里叶变换

我们可以精心地选择n个x的值,使得求值和插值均能够利用分治,从而达到 O ( N l o g N ) O(NlogN) O(NlogN)的时间复杂度。
(一). 单位复数根
先了解一下复数。复数包含实部和虚部。
如果把实部看做x轴,虚部看做y轴,则可以得到复平面。
则复平面上的任意点都表示一个复数。
对于一个最高项次数为n的方程,如: f ( x ) = a n x n + a n − 1 x n − 1 + ⋯ + a 1 x + a 0 = 0 f(x)=a_nx^n+a_{n-1}x^{n-1}+\dots+a_1x+a_0=0 f(x)=anxn+an1xn1++a1x+a0=0,如果考虑复数根,它一定有n个。

满足 x n = 1 x^n=1 xn=1的n个解,称为单位复数根。n次单位复根刚好有n个,这些根为
e 2 π i k / n e^{2\pi ik/n} e2πik/n.我们利用了复数的指数形式的定义.
e i ω = c o s ( ω ) + i s i n ( ω ) e^{i \omega}=cos(\omega)+isin(\omega) eiω=cos(ω)+isin(ω)
由此可见,这n个复根在复平面上是均匀分布在以原点为圆心的单位半径的圆周上.而其中 ω n = e 2 π i / n \omega_n=e^{2\pi i/n} ωn=e2πi/n,称为主n次单位根,所有其他的n次单位复根都是 ω n \omega_n ωn的幂.
单位复数根具备这样的性质:
(1).消去引理:对任何整数 n ≥ 0 , k ≥ 0 n\geq 0,k\geq 0 n0,k0,以及 d > 0 d \gt 0 d>0,有 ω d n d k = ω n k \omega_{dn}^{dk}=\omega_n^k ωdndk=ωnk.
(2).折半引理:如果 n > 0 n>0 n>0为偶数,那么n个n次单位复根的平方的集合就是n/2个n/2次单位复数根的集合.
利用消去引理即可证明.
(3).求和引理 对于任意整数 n ≥ 1 n\geq 1 n1和不能被n整除的非负整数k,有
∑ j = 0 n − 1 ( ω n k ) j = 0 \sum_{j=0}^{n-1}(\omega_n^k)^j=0 j=0n1(ωnk)j=0

(二).DFT
对于次数界为n的多项式, A ( x ) = ∑ j = 0 n − 1 a j x j A(x)=\sum_{j=0}^{n-1}a_jx^j A(x)=j=0n1ajxj
ω n 0 , ω n 1 , ω n 2 , … , ω n n − 1 \omega_n^0,\omega_n^1,\omega_n^2,\dots,\omega_n^{n-1} ωn0,ωn1,ωn2,,ωnn1处求值,得到向量 y = ( y 0 , y 1 , … , y n − 1 ) y=(y_0,y_1,\dots,y_{n-1}) y=(y0,y1,,yn1),则称y为系数向量 a = ( a 0 , a 1 , … , a n − 1 ) a=(a_0,a_1,\dots,a_{n-1}) a=(a0,a1,,an1)的离散傅里叶变换(DFT).记为 y = D F T n ( a ) y=DFT_n(a) y=DFTn(a)
(三).FFT
这是一种算法,可以在 O ( N l o g N ) O(NlogN) O(NlogN)的时间复杂度类计算出 D F T n ( a ) DFT_n(a) DFTn(a).它主要基于分治的策略。

A [ 0 ] ( x ) = a 0 + a 2 x 1 + ⋯ + a n − 2 x n / 2 − 1 A^{[0]}(x)=a_0+a_2x^1+\dots+a_{n-2}x^{n/2-1} A[0](x)=a0+a2x1++an2xn/21
A [ 1 ] ( x ) = a 1 x + a 3 x 1 + ⋯ + a n − 1 x n / 2 − 1 A^{[1]}(x)=a_1x+a_3x^1+\dots+a_{n-1}x^{n/2-1} A[1](x)=a1x+a3x1++an1xn/21
A ( x ) = A [ 0 ] ( x 2 ) + x A [ 1 ] ( x 2 ) A(x)=A^{[0]}(x^2)+xA^{[1]}(x^2) A(x)=A[0](x2)+xA[1](x2).
则A(x)在 ω n 1 , ω n 2 , ω n 3 , … , ω n n − 1 \omega_n^1,\omega_n^2,\omega_n^3,\dots,\omega_n^{n-1} ωn1,ωn2,ωn3,,ωnn1处的问题则转换为:
求次数界为n/2的多项式 A [ 0 ] ( x ) A^{[0]}(x) A[0](x) A [ 1 ] ( x ) A^{[1]}(x) A[1](x) ω n / 2 0 , ω n / 2 1 , … , ω n / 2 n / 2 − 1 \omega_{n/2}^0,\omega_{n/2}^1,\dots,\omega_{n/2}^{n/2-1} ωn/20,ωn/21,,ωn/2n/21处的值.
这里要求n为2的幂,这样才能保证 ω n 2 i = ω n / 2 i \omega_n^{2i}=\omega_{n/2}^i ωn2i=ωn/2i.

//递归写法,用于理解FFT。因为有更好的迭代写法,这种写法一般不用。
int *fft(int a[],int len)  //len is a power of 2
{ 
	if(len==1)
	return a;
	complex wn(cos(2*Pi/n),sin(2*Pi/n));
	complex w(1,0);
	static int a1[len/2+1],a2[len/2+1];
	static int y[len];
	for(int i=0;i

我们于是完成了对次数界为n的多项式在n次单位复根处的求值,从而得到它的点值表示法.这里的时间复杂度是O(NlogN)的.
接下来,我们可以通过点值表示法完成两个多项式的乘积,时间复杂度仍然是O(N)的.
然后,我们要把乘积的点值表示法换成系数表示法,这个过程是求值的逆运算,称为插值.
求值运算是这样的:
( 1 1 1 … 1 1 ω n 1 ω n 2 … ω n n − 1 1 ω n 2 ω n 4 … ω n 2 ( n − 1 ) ⋮ ⋮ ⋮ ⋮ ⋮ 1 ω n n − 1 ω n 2 ( n − 1 ) … ω n ( n − 1 ) ( n − 1 ) ) ( a 0 a 1 a 2 ⋮ a n ) = ( y 0 y 1 y 2 ⋮ y n − 1 ) \left(\begin{array}{c} 1& 1 & 1 & \dots &1 \\ 1& \omega_{n}^1 & \omega_n^2 & \dots & \omega_n^{n-1} \\ 1 & \omega_n^2 & \omega_n^4 & \dots & \omega_n^{2(n-1)} \\ \vdots & \vdots & \vdots &\vdots & \vdots \\ 1 & \omega_n^{n-1} & \omega_n^{2(n-1)} & \dots & \omega_n^{(n-1)(n-1)} \end{array}\right) \left(\begin{array}{c} a_0 \\ a_1 \\ a_2 \\ \vdots \\ a_n \end{array} \right)=\left(\begin{array}{c} y_0 \\ y_1 \\ y_2 \\ \vdots \\ y_{n-1} \end{array} \right) 11111ωn1ωn2ωnn11ωn2ωn4ωn2(n1)1ωnn1ωn2(n1)ωn(n1)(n1)a0a1a2an=y0y1y2yn1

左边的矩阵是范德蒙德矩阵, [ V ] i j = ω n i j [V]_{ij}=\omega_n^{ij} [V]ij=ωnij,可以构造出它的逆矩阵 V − 1 V^{-1} V1,其中 [ V − 1 ] j i = 1 / n ω n − i j [V^{-1}]_{ji}=1/n \omega_n^{-ij} [V1]ji=1/nωnij.
于是可以得到插值的运算是这样的:
( 1 / n 1 / n 1 / n … 1 / n 1 / n ω n − 1 / n ω n − 2 / n … ω n − ( n − 1 ) 1 / n ω n − 2 / n ω n − 4 / n … ω n − 2 ( n − 1 ) / n ⋮ ⋮ ⋮ ⋮ ⋮ 1 / n ω n − ( n − 1 ) / n ω n − 2 ( n − 1 ) / n … ω n − ( n − 1 ) ( n − 1 ) / n ) ( y 0 y 1 y 2 ⋮ y n − 1 ) = ( a 0 a 1 a 2 ⋮ a n ) \left(\begin{array}{c} 1/n& 1/n & 1/n & \dots &1/n \\ 1/n &\omega_{n}^{-1}/n & \omega_n^{-2}/n & \dots & \omega_n^{-(n-1)} \\ 1/n & \omega_n^{-2}/n &\omega_n^{-4}/n & \dots & \omega_n^{-2(n-1)}/n \\ \vdots & \vdots & \vdots &\vdots & \vdots \\ 1/n & \omega_n^{-(n-1)}/n & \omega_n^{-2(n-1)}/n & \dots & \omega_n^{-(n-1)(n-1)}/n \end{array}\right) \left(\begin{array}{c} y_0 \\ y_1\\y_2 \\ \vdots \\ y_{n-1} \end{array} \right)=\left(\begin{array}{c} a_0 \\ a_1 \\a_2 \\ \vdots \\ a_n \end{array} \right) 1/n1/n1/n1/n1/nωn1/nωn2/nωn(n1)/n1/nωn2/nωn4/nωn2(n1)/n1/nωn(n1)ωn2(n1)/nωn(n1)(n1)/ny0y1y2yn1=a0a1a2an
我们只需要在求值运算的基础上,稍作一些变换,将 a n a_n an y n y_n yn互换,用 ω n − 1 \omega_n^{-1} ωn1代替 ω n \omega_n ωn,并将计算结果中每一项除以n即可.
于是,我们也可以在 O ( N l o g N ) O(NlogN) O(NlogN)时间内求出 D F T n − 1 DFT_n^{-1} DFTn1.

(四).FFT的高效算法
上面的算法是递归的,我们可以把它改成迭代的.
观察向量 A = { a 0 , a 1 , … , a n } A=\{a_0,a_1,\dots,a_n\} A={a0,a1,,an},它每次都是按照奇偶分成两个向量 A [ 0 ] A^{[0]} A[0] A [ 1 ] A^{[1]} A[1].

快速傅里叶变换(FFT)_第1张图片
观察最底层系数的排列顺序,如果看他们的二进制,发现刚好0~7的二进制翻转,即镜面效果.为什么有这样的规律呢?
稍微一想,就能明白,因为我们是按照奇偶性来分的,就是按照最低位为0或为1来分的.所以排在前面的一半都是最低位为0的,后面的一般是最低位为1的.在每一半里,又是次低位为0的排前面,次低位为1 的排后面.如果把最低位看做最高位,次低位看做次高位,即将每个数镜面翻转,则它就是按照递增顺序排列的.也就是说,对于任何一个系数,如何它的编号是高低位镜面对称的,则它位置不变,否则,必然存在另一个系数,编号与它的编号镜面对称,则这两个数需要交换位置.这个置换我们称为位逆序置换,代码如下:

void change(complex num[],int len)
    {
        for(int i=1,j=len/2;i=k)
            {
                j-=k;
                k/=2;
            }
            if(j

接下来看一个简单的模板题:求a×b的结果。
其中a、b最多不超过50000位。
将a,b看做多项式f(x)和g(x),a*b即是将f(x)与g(x)两个多项式计算卷积,然后再令x=10的结果。
于是可以使用FFT。
注意不要压位太狠,系数有可能爆int。下面的代码没有压位。且保持读入的高低位顺序,即a1、a2数组从左到右即为读入的两个高精度数从高到低的顺序。

代码:
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAXN 200005
#define LL int
#define max(a,b) (a>b?a:b)
LL num1[MAXN],num2[MAXN],ans[MAXN];
char s1[MAXN],s2[MAXN];
int len1,len2,n;
const double PI=acos(-1.0);
struct complex
{
    double r,i;
    complex(double _r=0,double _i=0):r(_r),i(_i){}
    complex operator +(const complex &t)const
    {
        return complex(r+t.r,i+t.i);
    }
    complex operator -(const complex &t)const
    {
        return complex(r-t.r,i-t.i);
    }
    complex operator *(const complex &t)const
    {
        return complex(r*t.r-i*t.i,r*t.i+i*t.r);
    }
}a1[MAXN],a2[MAXN],w,wn;
void change(complex num[],int len)
    {
        for(int i=1,j=len/2;i=k)
            {

                j-=k;
                k/=2;
            }
            if(j0;i--)
        {
            ans[i-1]+=ans[i]/10;
            ans[i]%=10;
        }
        int ii;
       for(ii=0;ans[ii]==0&&ii

你可能感兴趣的:(数学)