首页 > 其他分享 >快速傅里叶变换

快速傅里叶变换

时间:2023-01-13 12:02:37浏览次数:39  
标签:rev limits omega 变换 傅里叶 sum 单位根 快速 2i

前前后后鸽了好久,终于抄完了。

单位根

概念

在复数下,满足 \(x^n = 1\) 的 \(x\) 称为 \(n\) 次单位根。

\(n\) 次单位根一共有 \(n\) 个。

将所有的单位根按照辐角大小排列,第 \(k\) 个(\(0 \leq k < n\))个 \(n\) 次单位根为:

\(x_k = e^{i \frac{2 k \pi}{n}}\)

所有的单位根模都是 \(1\),\(n\) 个单位根平分单位圆。

本原单位根:\(0\) 到 \(n - 1\) 次方的值能生成所有 \(n\) 次单位根的单位根称为 \(n\) 次本原单位根。

欧拉公式:\(e^{i \pi} = 1\)

\(x_1 = e^{i \frac{2 \pi}{n}}\) 是一个本原单位根。

记 \(n\) 次本原单位根为 \(\omega_n = e^{i \frac{2 \pi}{n}} = \cos \frac{2 \pi}{n} + i \sin \frac{2 \pi}{n}\)

性质

设 \(n\) 是一个正偶数,则:

\((\omega_n^k)^2 = w_{\frac{n}{2}}^k\)

\(\omega_{n}^{\frac{n}{2} + k} = -\omega_n^k\)

算法

求出一个 \(n\) 次多项式在每个 \(n\) 次单位根下的点值称为 离散傅里叶变换(DFT),而重新从这 \(n\) 个点值得到 \(n\) 次多项式的过程称为 离散傅里叶逆变换(IDFT)

对于要进行 FFT 的多项式,先将它的次数补到 \(2\) 的整次幂,记其次数为 \(n - 1\),令 \(m = \frac{n}{2}\)

DFT

考虑求一个长度为 \(n\) 的数列 \(b\),其中:

\(\sum\limits_{i = 0}^{n - 1} b_i = \sum\limits_{i = 0}^{n - 1} a_i \times \omega_n^{ik}\)

即这个数列的第 \(k\) 项是原多项式在 \(n\) 次单位根的 \(k\) 次幂处的取值。

FFT

FFT 是对 \(O(n^2)\) 的朴素 DFT 的优化。

考虑把上面的和式按照下标分类,即:

\(A(x) = \sum\limits_{i = 0}^{n - 1} a_i \cdot x_i = \sum\limits_{i = 0}^{m - 1} a_{2i} \cdot x^{2i} + \sum\limits_{i = 0}^{m - 1} a_{2i + 1} \cdot x^{2i + 1}\)

后半部分提出一个 \(x\),得:

\(A(x) = \sum\limits_{i = 0}^{n - 1} a_i \cdot x_i = \sum\limits_{i = 0}^{m - 1} a_{2i} \cdot x^{2i} + x \sum\limits_{i = 0}^{m - 1} a_{2i + 1} \cdot x^{2i} = \sum\limits_{i = 0}^{m - 1} a_{2i} \cdot (x^2)^i + x \sum\limits_{i = 0}^{m - 1} a_{2i + 1} \cdot (x^2)^i\)

设 \(A_0(x), A_1(x)\) 是两个 \(m - 1\) 次多项式,使得 \(A_0(x) = \sum\limits_{i = 0}^{m - 1} a_{2i} x^i, A_1(x) = \sum\limits_{i = 0}^{m - 1} a_{2i + 1} x^i\)

那么 \(A(x) = A_0(x^2) + x A_1(x^2)\)

这说明只要求出 \(A_0, A_1\) 在各处的点值,我们就可以在 \(O(n)\) 时间内计算 \(A\) 在各处的点值!并且子问题的结构是类似地,完全可以递归处理!

但是这样递归下去的复杂度是 \(O(n^2)\)

所以我们可以考虑一下单位根的性质:

\((\omega_n^k)^2 = \omega_m^k\)

\(\omega_{n}^{m + k} = -\omega_n^k\)

考虑 \(m\) 次以内的点值和高于 \(m\) 次的点值之间的关系:

\(\forall 0 \leq k < m, A(\omega_n^k) = A_0((\omega_n^k)^2) + \omega_n^k A_1((\omega_n^k)^2)\)

根据第一个式子化简得到:

\(A(\omega_n^k) = A_0(\omega_m^k) + \omega_n^k A_1(\omega_m^k)\)

对于大于等于 \(m\) 次的点值:

\(A(\omega_n^{m + k}) = A_0((\omega_n^{m + k})^2) + \omega_n^{m + k} A_1((\omega_n^{m + k})^2)\)

根据第二个式子化简得到:

\(A(\omega_n^{m + k}) = A_0((\omega_n^k)^2) - \omega_n^k A_1((\omega_n^k)^2)\)

再根据第一个式子得到:

\(A(\omega_n^{m + k}) = A_0(\omega_m^k) - \omega_n^k A_1(\omega_m^k)\)

比对一下:\(A(\omega_n^k) = A_0(\omega_m^k) + \omega_n^k A_1(\omega_m^k)\)

差别只有后半部分的系数!!!1

所以我们只需要把小于 \(m\) 次的部分递归处理出来,然后再处理后半部分即可。

时间复杂度优化到 \(O(n \log n)\)

上面的推导称为 蝴蝶操作

IDFT

不太会证明,暂且先放个结论,等以后再回来填坑。

假设当前已知一个 \(n - 1\) 次多项式 \(A(x) = \sum\limits_{i = 0}^{n - 1} a_i \cdot x^i\) 经过 DFT 后得到的点值序列 \(b\),则:

\(b_k = \sum\limits_{i = 0}^{n - 1} a_i \cdot \omega_n^{ik}\)

结论是:\(a_k = \frac{1}{n} \sum\limits_{i = 0}^{n - 1} b_i \omega_n^{-ki}\)

这里有一个消去引理的知识,之后再补回来。

IFFT

如何快速求 \(B\) 在 \(\omega_n^{-ki}\) 处的取值?

根据几何意义易知:\(\omega_n^{-k} = \omega_n^{n - k}\)

所以只需要正常求 \(B\) 在 \(\omega_n^k\) 处的取值,然后对数组取反即可,magic!

优化

递归改成迭代会快很多。

考虑画一下递归调用的原数组下标:

stage 1: 0 1 2 3 4 5 6 7
stage 2: 0 2 4 6|1 3 5 7
stage 3: 0 4|2 6|1 5|3 7
stage 4: 0|4|2|6|1|5|3|7

人类智慧:stage 4 二进制写法反过来刚好是:

000, 001, 010, 011, 100, 110, 101, 111

也就是 01234567,magic!

所以只需要把原本的 \(a\) 数组按照二进制下标反转后的大小排序,倒数第二层的蝴蝶操作就是合并 \(a\) 中相邻的项。

用类似数位 dp 的方法可以快速求出每个值二进制反转后的值:

rev[i] = (rev[i >> 1] >> 1 | (i & 1 ? k >> 1 : 0));

因为 \(rev(rev(i)) = i\),所以对于一个下标 \(p\),若 \(rev(p) = q\),那么 \(rev(q) = p\)。所以只需要对于 \(rev(i) > i\) 的位置交换一下就能排序力,这个称为位逆序置换。

代码

#include <cstdio>
#include <cmath>
#include <complex>
#include <iostream>
#include <algorithm>
using namespace std;

const int sz = 5e6 + 5;
const double PI = acos(-1);

int n, m;
int rev[sz];
complex<double> F[sz], G[sz];

void calc_rev(int k) { for (int i = 0; i < k; i++) rev[i] = (rev[i >> 1] >> 1 | (i & 1 ? k >> 1 : 0)); }

void FFT(complex<double> *A, int n)
{
    for (int i = 1; i < n; i++)
        if (rev[i] > i) swap(A[i], A[rev[i]]);
    for (int len = 2, m = 1; len <= n; m = len, len <<= 1)
    {
        complex<double> W(cos(PI / m), sin(PI / m)), w(1.0, 0.0);
        for (int l = 0, r = len - 1; r <= n; l += len, r += len)
        {
            auto w0 = w;
            for (int p = l; p < l + m; p++)
            {
                auto x = A[p] + w0 * A[p + m], y = A[p] - w0 * A[p + m];
                A[p] = x, A[p + m] = y;
                w0 *= W;
            }
        }
    }
}

void IFFT(complex<double> *A, int n)
{
    FFT(A, n);
    reverse(A + 1, A + n);
}

int main()
{
    scanf("%d%d", &n, &m);
    for (int i = 0, v; i <= n; i++) scanf("%d", &v), F[i] = v;
    for (int i = 0, v; i <= m; i++) scanf("%d", &v), G[i] = v;
    int len = n + m, k = 1;
    while (k <= len) k <<= 1;
    calc_rev(k);
    FFT(F, k), FFT(G, k);
    for (int i = 0; i < k; i++) F[i] *= G[i];
    IFFT(F, k);
    for (int i = 0; i <= len; i++) printf("%d ", (int)(F[i].real() / k + 0.5));
    return 0;
}

标签:rev,limits,omega,变换,傅里叶,sum,单位根,快速,2i
From: https://www.cnblogs.com/lingspace/p/fft.html

相关文章

  • WindTerm快速切换标签页
    默认使用Alt+[和Alt+]进行切换,如需修改常规的Ctrl+Tab进行如下操作找到快捷键配置文件WindTerm_2.6.0\global下的wind.keymaps并打开。如果找不到的话,用everything搜......
  • OpenHarmony开发环境快速搭建(无需命令行)
    一.搭建Windows环境在嵌入式开发中,很多开发者习惯于使用Windows进行代码的编辑,比如使用Windows的VisualStudioCode进行OpenHarmony代码的开发。但当前阶段,大部分的开发板......
  • spring boot——请求与参数校验——Filter——Filter快速入门
         ......
  • Python实现希尔排序、快速排序、归并排序
    快速排序快速排序(英语:Quicksort),又称划分交换排序(partition-exchangesort),通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都......
  • 【假期刷些题-1】快速幂、递归幂次方、音速、快乐水
    因为很久没刷算法题了手有点生,所以刷些题记录下快速幂和取余运算//取模运算的性质(a*b)%p=[(a%p)*(b%p)]%p快速幂模板题https://www.luogu.com.cn/problem/P1226对......
  • uwsgi 快速入门
    目录uwsgi快速入门一、概述1、简单介绍2、环境配置二、第一个WSGI应用1、运行2、添加并发三、结合Web服务器使用1、Flask2、Django3、Nginx配置uwsgi快速......
  • 【数学1】快速幂与龟速乘
    快速幂与龟速乘一、快速幂1.算法原理求\(a^b\bmodp\)的结果。我们可以构造如下算法:\[a^b\bmodp=\begin{cases}(a^{\fracb2})^2\bmodp&\texttt{biseven}\\a......
  • 洛谷 P8077 [WC2022] 序列变换 题解
    题目链接。WC2023之前补一下WC2022的题,参考了官方题解。首先,把括号序列转化为二叉树,\(\texttt{(A)B}\)转为一个点的左子树是\(A\),右子树是\(B\)。相当于括号序列先......
  • 快速入门前端图表插件E-chart
    在前端项目开发中,有很多地方会遇到绘制图表的需求,一般的图表可以通过canvas来绘制,但是遇到复杂一点的图表怎么办呢?而且黑马的课程大纲已经把canvas课程删掉了,既然canvas有用......
  • 快速利用ceph对象存储与owncloud打造储存“云盘”
        6, 文件上传下载测试 [root@client ~]#s3cmdput/etc/fstabs3://owncloud upload: '/etc/fstab' -> 's3://owncloud/fstab'             ......