多项式乘法——FFT 快速傅里叶变换

news/2025/2/23 22:31:11/文章来源:https://www.cnblogs.com/Young-Cloud/p/18729235

问题引入

现在有两个多项式: \(A(x) = \sum _{i = 0}^{n - 1} a_i x^i\)\(B(x) = \sum _{i = 0}^{m - 1} b_i x^i\),我们的目的是要求这两个多项式相乘后得到的多项式 \(C(x)\)\(C(x) = \sum _{i = 0}^{n + m - 1} c_i x^i\)

初步分析

要求解 \(C(x)\),就是要求得 \(C(x)\) 每一项的系数 \(c_i\)

若暴力的计算 \(c_i\),那么 \(c_i = \sum _{j = 0}^i a_j b_{i - j}\),总的时间复杂度是 \(O(nm)\) 的。

我们知道,直角坐标系上 \(n\) 个不同点可以确定一个过这 \(n\) 个点的 \(n - 1\) 次多项式。也就是说对于 \(C(x)\) 来讲,只要确定了它经过的 \(n + m\) 个不同的点的坐标,我们就有办法确定 \(C(x)\) 这个多项式。于是我们取 \(n + m\) 个不同的点 \(\{ (x_0, C(x_0)), (x_1, C(x_1)), \dots , (x_{n + m - 1}, C(x_{n + m - 1})) \}\),其中 \(C(x_i) = A(x_i)B(x_i)\),即我们要在 \(A(x)\)\(B(x)\) 上分别取 \(n + m\) 个点。如果暴力地取点时间复杂度还是 \(O(n^2)\),而且我们还不知道当取完点后如何同过这些点确定 \(C(x)\)

若果有方法能够快速地取点(DFT)并快速地根据点来确定多项式(IDFT),那我们就解决了问题。

取点——DFT 离散傅里叶变换

此处用“取点”表示其他博文中的“将系数表达式映射到点值表达式”(因为我感觉这个过程就是在取点)

DFT 的定义

因为光看FFT代码搞不清FFT与傅里叶变换的关系,于是有了这一部分

离散傅里叶变换将长度为 \(n\) 的时域信号 \(x[t]\) 转换为频域信号 \(X[k]\),其数学表达式为:

\[X[k] = \sum_{t = 0} ^{n - 1} x[t] e^{-i{2 \pi \over n}kt} \]

其中:

  • \(x[t]\) 表示时域信号的第 \(t\) 个采样点
  • \(X[k]\) 表示频域信号的第 \(k\) 个频率分量

那这坨公式和我们的多项式有什么关系呢?

抛开定义中的物理意义,假设有个多项式 \(P(u) = \sum_{t = 0} ^{n - 1} x[t] u^t\)(也就是说把定义中的时域信号看作是多项式的系数),于是 \(P(e^{-i{2 \pi \over n}k}) = \sum_{t = 0} ^{n - 1} x[t] (e^{-i{2 \pi \over n}k})^t = X[k]\)。此时对多项式取点的操作就与 DFT 产生了联系:对于一个 \(n - 1\) 次多项式,我们可以对其进行 DFT 以求得这个多项式在 \(e^{-i{2 \pi \over n}0},e^{-i{2 \pi \over n}1},e^{-i{2 \pi \over n}2}, \dots ,e^{-i{2 \pi \over n}(n - 1)}\)\(n\) 个位置上的值,这 \(n\) 个点其实就是单位根

但是如果按照定义直接每个点单独求值,时间复杂度还是 \(O(n^2)\) 的,并没有什么优化,于是就有 FFT。

(PS:在问题分析中我们提到要在 \(A(x)\)\(B(x)\) 上分别取 \(n + m\) 个点,为了解决这个问题,我们可以在两个多项式后面添项,如:\(A(x) = (\sum _{i = 0}^{n - 1} a_i x^i) + 0 \times x^n + \dots + 0 \times x^{n + m - 1}\)

FFT——DFT 的高效实现

(观看视频 BV1za411F76U 可以比较清楚地知道 FFT 的实现。)

FFT 是 DFT 数学公式的高效实现,通过分治和对称性减少计算量。

我们将原本的多项式(这里是添项过后的,而且项数 \(n\) 是 2 的幂) \(A(x) = \sum _{i = 0}^{n - 1} a_i x^i\) 的系数分成两部分,分别构成两个新的多项式 \(A_e(x) = \sum _{i = 0}^{\frac{n}{2} - 1} a_{2i} x^i\)\(A_o(x) = \sum _{i = 0}^{\frac{n}{2} - 1} a_{2i + 1} x^i\)。(角标的含义是 even 和 odd)。

\(A(x)\) 还可以这样表示:

\[A(x) = A_e(x^2) + x A_o(x^2) \]

那么代入单位根 \(w_n^k\) 得到:

\[A(w_n^k) = A_e((w_n^k)^2) + w_n^k A_o((w_n^k)^2) \]

\[A(w_n^k) = A_e(w_{n \over 2}^k) + w_n^k A_o(w_{n \over 2}^k) \]

而且我们不需要从 0 到 \(n - 1\) 取枚举 \(k\),因为对于 \(k < {n \over 2}\),有 \(w_n^{k + {n \over 2}} = -w_n^k\),即:

\[A(w_n^{k + {n \over 2}}) = A(-w_n^k) = A_e(w_{n \over 2}^k) - w_n^k A_o(w_{n \over 2}^k) \]

所以我们只需要枚举前一半就好了。

对于 \(A_e(w_{n \over 2}^k)\)\(A_o(w_{n \over 2}^k)\),同样用 FFT,递归的求就可以了,当项数为 1 时直接返回系数或者什么都不做就好了。

于是就可以写出递归版的 FFT (代码等说了 IDFT 再给出)

确定多项式——IDFT 离散傅里叶逆变换

在 DFT 中我们实际是解了一个这样的方程:

\[\begin{bmatrix} (w_n^0)^0&(w_n^0)^1&\dots&(w_n^0)^{n - 1}\\ (w_n^1)^0&(w_n^1)^1&\dots&(w_n^1)^{n - 1}\\ \vdots&\vdots& &\vdots\\ (w_n^{n - 1})^0&(w_n^{n - 1})^1&\dots&(w_n^{n - 1})^{n - 1}\\ \end{bmatrix} \begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n - 1} \end{bmatrix} = \begin{bmatrix} A(w_n^0)\\ A(w_n^1)\\ \vdots\\ A(w_n^{n - 1})\\ \end{bmatrix} \]

即求解了 \(A(w_n^i)\)

那么在 IDFT 中我们知道了 \(A(w_n^i)\) 要求 \(a_i\)。在方程两边同时左乘第一个矩阵的逆矩阵(求逆矩阵直接搜范德蒙矩阵的逆矩阵就好了,虽然有点区别,但直接类比过来就好了)得到:

\[\begin{bmatrix} a_0\\ a_1\\ \vdots\\ a_{n - 1} \end{bmatrix} = {1 \over n}\begin{bmatrix} (w_n^0)^0&(w_n^0)^{-1}&\dots&(w_n^0)^{-(n - 1)}\\ (w_n^1)^0&(w_n^1)^{-1}&\dots&(w_n^1)^{-(n - 1)}\\ \vdots&\vdots& &\vdots\\ (w_n^{n - 1})^0&(w_n^{n - 1})^{-1}&\dots&(w_n^{-(n - 1)})^{-(n - 1)}\\ \end{bmatrix} \begin{bmatrix} A(w_n^0)\\ A(w_n^1)\\ \vdots\\ A(w_n^{n - 1})\\ \end{bmatrix} \]

这几乎是和 DFT 一样的问题,只不过 IDFT 中的单位根要和 DFT 中的单位根共轭,并且最后还要除 \(n\)

于是就可以得到递归版的代码:

// len表示项数,并且一定是 2 的幂
void fft(int len, std::vector<std::complex<double>> &A, bool inv) {if (len == 1) {return;}int ll = len >> 1;std::vector<std::complex<double>> Ae(ll), Ao(ll);for (int i = 0; i + i < len; i++) {Ae[i] = A[i << 1];Ao[i] = A[i << 1 | 1];}fft(ll, Ae, inv);fft(ll, Ao, inv);double angle = 2.0 * M_PI / double(len);std::complex<double> unit(cos(angle), (inv ? -1 : 1) * sin(angle)), w(1.0, 0.0);for (int i = 0; i < ll; i++, w *= unit) {std::complex<double> tmp = w * Ao[i];A[i] = Ae[i] + tmp;A[i + ll] = Ae[i] - tmp;}// 当使用 IDFT 时,别忘了在函数外边另外除项数。return;
}

有了最基础递归版的 FFT,就可以将 P3803 【模板】多项式乘法(FFT) 解决了。(题解里面有挺多很好的题解的)

(迭代版的 FFT 和 NNT 之后再学,逃~)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/888600.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

工程管理(二)

工程模板介绍 DevEco Studio支持多种品类的应用/元服务开发,预置丰富的工程模板,可以根据工程向导轻松创建适应于各类设备的工程,并自动生成对应的代码和资源模板。同时,DevEco Studio还提供了多种编程语言供开发者进行应用/元服务开发,包括ArkTS、JS和C/C++。工程模板支持…

工程管理(一)

APP包结构 在进行应用/元服务开发前,开发者应该掌握应用/元服务的逻辑结构。 应用/元服务发布形态为APP Pack(Application Package),它是由一个或多个HAP(Harmony Ability Package)包以及描述APP Pack属性的pack.info文件组成。 一个HAP在工程目录中对应一个Module,它是…

包和抽象类介绍--java进阶day02

1.package包导包第二点需要注意 a包和b包都存有Student类,c包存有测试类,我们在c中创建Student对象,系统会询问你要哪个包的Student类,并自动帮你导包.在导完a包的学生类后,想要再次导入b包的学生类就不能再像之前那样导了全类名导包 通过带包名将b包重复的学生类导入2.抽…

2025.2.23(二进制等等)

平常我们生活使用的是十进制,在计算机中常用二进制等。 二进制是用0,1表示,逢二进1. 啊啊啊好难表达。 例如2在二进制中为10.哎上图片。。。。除2取余法,哎呀,不管了看图

Deveco Studio下载

Deveco Studio最新版本-下载中心根据自己的操作系统下载合适的版本即可 Windows环境 运行环境要求 为保证DevEco Studio正常运行,建议电脑配置满足如下要求:操作系统:Windows10 64位、Windows11 64位 内存:16GB及以上 硬盘:100GB及以上 分辨率:1280*800像素及以上安装Dev…

第一次作业—软件二次开发

一.项目来源 本次作业的项目来源是https://blog.csdn.net/m0_65636467/article/details/128069045?sharetype=blog&shareId=128069045&sharerefer=APP&sharesource=2301_80676751&sharefrom=link中的第7个C语言超市收款系统 二.运行环境和运行结果 1.运行环境…

《痞子衡嵌入式半月刊》 第 118 期

痞子衡嵌入式半月刊: 第 118 期这里分享嵌入式领域有用有趣的项目/工具以及一些热点新闻,农历年分二十四节气,希望在每个交节之日准时发布一期。 本期刊是开源项目(GitHub: JayHeng/pzh-mcu-bi-weekly),欢迎提交 issue,投稿或推荐你知道的嵌入式那些事儿。 上期回顾 :《…

【牛客训练记录】牛客周赛 Round 82

训练情况赛后反思 C题没想明白,但是发现了数列一定是不增加的,另外第一次出现的数字,那个位置就必须是那个数字,剩下可能是乘法原理之类的东西吧,但是没做出来 A题 判断字符串第一位和最后一位是否一致即可点击查看代码 #include <bits/stdc++.h> // #define int lo…

【Atcoder训练记录】AtCoder Beginner Contest 394

训练情况赛后反思 没在赛时打的,只做了签到TAT A题 统计字符串中 2 的数量,最后去掉其他的,只输出 2点击查看代码 #include <bits/stdc++.h> // #define int long long #define endl \nusing namespace std;void solve(){string s; cin>>s;int ans = 0;for(int …

DPDK收发包梳理

DPDKeal初始化 内存管理:大页,内存池 驱动开启调试信息 make config T=x86_64-native-linuxapp-gcc export EXTRA_CFLAGS=-O0 -g3 -ggdb make -j8 dpdk通过makefile编译 meson + ninja没学过,太麻烦了,可以参考dpdk17的文档,里面有介绍make编译方式。 https://doc.dpdk.or…

空气流量和空气压力参数解耦系统simulink建模与仿真

1.课题概述空气流量和空气压力参数解耦系统simulink建模与仿真,在许多系统中,空气流量(Q)和压力(P)之间存在耦合关系,这意味着改变一个参数会影响到另一个参数。通过解耦系统解决这种问题,从而提高系统的控制稳定性。2.系统仿真结果 (完整程序运行后无水印)3.核心程序…

【库】Coravel Cache缓存

Coravel 通过使高级应用程序功能(如任务/作业调度、排队、缓存、邮件(以及更多!))易于访问且易于使用,帮助开发人员快速启动并运行 .NET 应用程序。具有简单、富有表现力和直接的语法。Coravel非常简单,通过Rember来保存缓存数据,同时可以设定缓存的时长,然后通过Get来…