多项式-快速傅里叶变换
解决问题
求解两个多项式相乘。
即对于两个 项多项式
求出
其中
以上, 为一个足够大的值。
前置知识
-复数
请自行学习复数相关知识。
- 单位根
将复平面单位圆 等分,规定 为 次单位根,逆时针依次标号为 。
可以发现 即为 。
由此可以证明以下=几条性质(当然也可以在单位圆上数形结合):
单位根反演
证:
时 ,
则
时 ,
则
又根据等比数列求和公式:
有
两种情况综合,得证。
韦达定理
个点确定一个 项多项式。
过程
如果我们得知 个形如
的点值,便可以得出 的点值表示:
然后我们便可以确认 的系数。
将系数表示法转化为点值表示法的过程称为 ,逆过程称为 。
DFT
对于上述一个多项式,我们将它拆开来看:
将每一项按照奇偶性分离:
假定我们有另外两个多项式:
可以发现:
好的,我们将它推成了很适合分治的形式,但这还不够。
接下来我们将 代入看看能得到什么:
令 ,代入 和 得到:
我们惊喜地发现,只需要将 分别代入 和 ,便可以得出 所有的点值。
每次问题的规模都被缩小一半,复杂度满足
即 。
另外对于具体实现,发现我们需要保证 为 的整次幂。
IDFT
对于 的其中一项 ,有:
则:
将 得到的 和 的点值相乘作为系数,在分别代入 ,得到的结果在除以 ,便得到了对应的系数。
如何代入 ,详情见代码:
#include <bits/stdc++.h>
#define lep(i, a, b) for (int i = a; i <= b; ++i)
#define rep(i, a, b) for (int i = a; i >= b; --i)
typedef std::complex<double> cp;
typedef long long ll;
const int _ = 4e6 + 7;
const double PI = std::acos(-1);
int n, m;
cp A[_], B[_], b[_];
void FFT(cp a[], int n, int opt) {
if (n == 1) return; int mid = (n >> 1);
lep(i, 0, mid - 1) b[i] = a[i << 1], b[i + mid] = a[i << 1 | 1];
lep(i, 0, n - 1) a[i] = b[i];
FFT(a, mid, opt), FFT(a + mid, mid, opt);
cp wk = cp(1.0, 0.0), w1 = cp(std::cos(1.0 * PI / mid), opt * std::sin(1.0 * PI / mid));//共轭,虚部取负
lep(i, 0, mid - 1) {
b[i] = a[i] + wk * a[i + mid],
b[i + mid] = a[i] - wk * a[i + mid];
wk *= w1;
}
lep(i, 0, n - 1) a[i] = b[i];
}
int main() {
std::ios::sync_with_stdio(false),
std::cin.tie(nullptr), std::cout.tie(nullptr);
std::cin >> n >> m;
lep(i, 0, n) std::cin >> A[i];
lep(i, 0, m) std::cin >> B[i];
m = n + m, n = 1;
while (n <= m) n <<= 1;
FFT(A, n, 1), FFT(B, n, 1);
lep(i, 0, n - 1) A[i] *= B[i];
FFT(A, n, -1);
lep(i, 0, m) std::cout << (int)(A[i].real() / n + 0.5) << ' ';
return 0;
}
蝴蝶变换优化
上述是常见的 递归写法,但其实,我们可以将其写成常数更小的非递归写法。
对于一个 项多项式,递归过程中的系数变化如下:
s: a_0 a_1 a_2 a_3 a_4 a_5 a_6 a_7
1: 000 001 010 011 100 101 110 111
2: 000 010 100 110 | 001 011 101 111
3: 000 100 | 010 110 | 001 101 | 011 111
t: a_0 a_4 a_2 a_6 a_1 a_5 a_3 a_7
可以发现,递归分奇偶的过程就是将 与 交换。
其中 的二进制表示为 的二进制表示的翻转( 位表示)。
所以我们可以直接处理处每一个系数最后会递归到哪一个位置,然后从下至上递推答案。
定义 为 二进制表示翻转后的十进制表示,有递推式如下:
(想一想,为什么)
迭代版代码如下:
void Init() {
m = n + m, n = 1;
while (n <= m) n <<= 1, ++l;
lep(i, 1, n - 1) f[i] = (f[i >> 1] >> 1) | ((i & 1) << (l - 1));
}
void FFT(cp a[], int n, int opt) {
lep(i, 1, n - 1) if (i < f[i]) std::swap(a[i], a[f[i]]);
for (int mid = 1; mid < n; mid <<= 1) {
for (int j = 0; j < n; j += (mid) << 1) {
cp wk = cp(1, 0), w1 = cp(std::cos(PI / mid), opt * std::sin(PI / mid));
lep(k, 0, mid - 1) {
b[j + k] = a[j + k] + wk * a[j + k + mid],
b[j + k + mid] = a[j + k] - wk * a[j + k + mid];
wk *= w1;
}
}
lep(i, 0, n - 1) a[i] = b[i];
}
}
时间仓促,如有错误欢迎指出,欢迎在评论区讨论,如对您有帮助还请点个推荐、关注支持一下
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效