• FFT学习笔记


    FFT学习笔记

    FFT,快速傅里叶变换,是一种在(O(nlog n))的时间内计算两个多项式乘积的算法。

    前置芝士

    复数

    没学过的请自行翻阅高中数学选修2-2。

    多项式的表示

    给出一个多项式 $f(x) = a_0 + a_1x + a_2x^2 + ... + a_nx^n $

    系数表示法

    就是用一个系数序列来表达多项式,显然系数序列和多项式是一一对应的。

    [f(x) = a_0 + a_1x + a_2x^2 + ... + a_nx^n Leftrightarrow f(x) = {a_0,a_1,a_2,...,a_n} ]

    点值表示法

    点值表示法是把这个多项式看成一个函数,从上面选取(n+1)个点,从而利用这 (n+1) 个点来唯一的表示这个函数。

    正如解一个 (n) 元方程组需要 (n+1) 个方程一样,这 (n+1) 个点的序列显然也和多项式一一对应。

    [f(x) = a_0 + a_1x + a_2x^2 + ... + a_nx^n Leftrightarrow f(x) = {(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_n,y_n)} ]

    --------------------分割线--------------------

    这两种表示法各有优劣,系数表示法可以在 (O(n)) 的时间内求出一个 (f(x_0)) ,但计算多项式乘法时却需要 (O(n^2)) 的时间,点值表示法则正好相反。所以FFT的目的就是利用一些特殊的点,把复杂度均摊一下,变成 (O(nlog n))

    单位复根

    这是FFT降低复杂度的关键,因为单位复根有极为优秀的性质。

    定义:

    对于方程 (x^n = 1) 的复数解,称之为 (n) 次单位复根,显然这样的单位复根有 (n) 个。

    其中一个可以表示为 $ omega_n = e^{frac{2pi i}{n}} $ 那么对于每一个单位复根,有$$ omega_n^k = e^{frac{2pi i k}{n}} $$

    而根据欧拉公式,又有:

    [omega_n = e^{frac{2pi i}{n}} = cos(frac{2 pi}{n}) + i · sin(frac{2 pi}{n}) ]

    几何意义:

    在一个单位圆中,把单位圆等分,几个等分点的坐标就是单位复根的向量。

    这个图就可以生动形象表示出来了。

    单位根的性质

    性质1:(omega_n^0 = 1)

    这十分显然,看图就知道了。

    性质2:(omega_n^k = omega_{2n}^{2k})

    可以根据公式证明,或者几何意义也可直观理解。

    性质3:(omega_n^{k+frac{n}{2}}=-omega_n^k)

    仍然是代入公式证明即可。

    这三个性质就是FFT降低复杂度的关键了。

    快速傅里叶变换

    离散傅里叶变换(DFT)

    对于一个多项式 $f(x) = a_0 + a_1x + a_2x^2 + ... + a_{n-1}x^{n-1} $

    为了便于操作,设 (n = 2^k)$ ,不足补0即可。

    然后按照奇偶项分类,奇数项提出一个 (x)

    [f(x) = (a_0 + a_2x^2 + ... + a_{n-2}x^{n-2}) + x(a_1 + a_3x^2 + ... + a_{n-1}x^{n-2} ]

    设两个新的多项式:

    [g(x) = a_0 + a_2x + ... + a_{n-2}x^{frac{n}{2}} ]

    [h(x) = a_1 + a_3x + ... + a_{n-1}x^{frac{n}{2}} ]

    那么就可以得到:

    [f(x) = g(x^2)+x·h(x^2) ]

    (DFT(f(x)))表示对 (f(x)) 进行DFT操作后得到的点值表示,则:

    [DFT(f(x)) = DFT(g(x^2))+x·DFT(h(x^2)) ]

    重要的步骤出现了!这时我们依次代入单位复根:

    [DFT(f(omega_n^k)) = DFT(g((omega_n^k)^2))+omega_n^k·DFT(h((omega_n^k))^2)) ]

    [DFT(f(omega_n^k)) = DFT(g(omega_n^{2k}))+omega_n^k·DFT(h(omega_n^{2k})) ]

    [DFT(f(omega_n^k)) = DFT(g(omega_{frac{n}{2}}^{k}))+omega_n^k·DFT(h(omega_{frac{n}{2}}^{k})) ]

    同理:

    [DFT(f(omega_n^{k+frac{n}{2}})) = DFT(g(omega_{frac{n}{2}}^{k}))-x·DFT(h(omega_{frac{n}{2}}^{k})) ]

    我们发现,只要我们求出了(DFT(g(omega_{frac{n}{2}}^{k})))(DFT(h(omega_{frac{n}{2}}^{k}))),就可以求出 (DFT(f(omega_n^k)))(DFT(f(omega_n^{k+frac{n}{2}}))),而这是一个问题规模不断变小的子问题,直接递归就行了。

    复杂度分析:

    代入操作要进行 (n) 次,花费 (O(n)) 的时间,设单次操作复杂度为 (T(n)),则有:

    [T(n) = 2T(frac{n}{2}) +O(n) ]

    根据主定理,可得复杂度为 (O(n log n))

    离散傅里叶逆变换(IDFT)

    证明过程又臭又长,这里就不写了qwq。(参考链接)

    只介绍算法:

    对于一个点值序列:(f(x) = {(x_0,y_0),(x_1,y_1),(x_2,y_2),...,(x_n,y_n)})

    进行的操作方法和DFT类似,只是每次代入(frac{1}{omega_n^k})即可。

    化简一下:

    (frac{1}{omega_n^k} = e^{-frac{2pi ik}{n}} = cos(frac{2 pi k}{n}) + i · sin(-frac{2 pi k}{n}))

    我们注意到这个值和DFT代入的值仅仅只有符号上的区别,那么不妨定义一个变量(op),当 (op)(1) 时进行 DFT操作,取 (-1) 就是IDFT操作。

    代码实现:

    void fft(comp *f,int n,int op) // op=1 -> DFT,op=-1 -> IDFT
    {
    	if(n == 1) return;//递归边界
    	for(int i = 0;i < n;++i) tmp[i] = f[i];
    	for(int i = 0;i < n;++i)//奇偶分别处理
    	{
    		if(i & 1) f[i / 2 + n / 2] = tmp[i];
    		else f[i / 2] = tmp[i];
    	}
    	comp *g = f,*h = f + n / 2;
    	fft(g,n / 2,op);fft(h,n / 2,op);//递归
    	comp w(cos(2 * pi / n),sin(2 * pi * op / n)),x(1,0);//单位根
    	for(int k = 0;k < n / 2;++k)
    	{
    		tmp[k] = g[k] + x * h[k];
    		tmp[k + n / 2] = g[k] - x * h[k];
    		x *= w;
    	}
    	for(int i = 0;i < n;++i) f[i] = tmp[i];
        return;
    }
    

    好的最朴素的FFT就讲完了。

    FFT优化

    咕咕咕咕咕咕咕咕咕

  • 相关阅读:
    CodeForces 1059B
    CodeForces 714A
    浅析母函数
    CodeForces 816C 思维
    CodeForces 816B 前缀和
    CodeForces
    Java项目读取resources资源文件路径那点事
    原型模式
    一次给女朋友转账引发我对分布式事务的思考
    连续最大字段和问题
  • 原文地址:https://www.cnblogs.com/lijilai-oi/p/12708442.html
Copyright © 2020-2023  润新知