FFT&NTT
Part 1 FFT
网上FFT和NTT的博客基本都是先铺一大堆前置知识,直接劝退,但其实FFT是个很好理解的东西,也不需要那些前置知识。
那些前置知识无非就是引出单位根,但我们可以直接定义单位根。
只需要记住, 次单位根 是一个满足如下性质的数:
- 。
如果要求的话,记住 。是个复数。
在计算多项式 的乘积时,FFT先将 分别转为点值表示,直接相乘得到结果的点值表示,再转回系数表示。为了方便,先把 的长度补到 的整数次幂。
FFT 的点值不是随便取的,是将 分别带入得到的。
如果要计算 ,设 。不妨把 的每个项按奇偶性分类,设两个多项式 ,那么根据上面的性质可以很轻松地得到:
因此,只需要计算出 在 处的取值即可 求出 在 处的取值。
都是规模减半的子问题,所以可以递归处理,时间复杂度 。
Part 2 IFFT
问题来了,怎么把一个点值表示出的多项式转回系数表示呢。
假设有一个多项式 ,我们求出了它在 处的每个取值 。
设一个多项式 。把单位根的倒数,即 带入,得到的点值依次是 。
而 当 时为 ,否则等比数列求和后这东西就是 。
因此,,即 。
现在就可以通过 求出 了。不难发现把单位根倒数带进去和把单位根带进去并没有什么区别。
Part 3 NTT
其实就是用原根顶替了单位根。
虽然不能这么说,但你可以直接认为 在模 意义下为 。 被我们补为了 ,所以常用的模数 998244353 你会发现它等于 乘上一坨。
然后NTT和FFT除了好写了点以为就没有区别了。
FFT的迭代优化和蝴蝶操作在NTT中效果貌似都不明显?
贴个NTT的代码:
#include <cstdio>
#define gc (p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, 100000, stdin), p1 == p2) ? EOF : *p1 ++)
char buf[100000], *p1, *p2;
inline int read() {
char ch;
int x = 0;
while ((ch = gc) < 48);
do x = x * 10 + ch - 48; while ((ch = gc) >= 48);
return x;
}
const int mod = 998244353, G = 3;
inline void add(int &x, int y) {(x += y) >= mod && (x -= mod);}
inline int mns(int x, int y) {return x >= y ? x - y : x - y + mod;}
inline int qpow(int a, int b) {
int ret = 1;
while (b) {
if (b & 1) ret = 1ll * ret * a % mod;
a = 1ll * a * a % mod, b >>= 1;
}
return ret;
}
int omega[22][3100000], inv[22][3100000], Log[3100000], rev[3100000];
inline void swap(int &x, int &y) {
int t = x; x = y; y = t;
}
void NTT(int *a, int n, int type) {
for (int i = 0; i < n; ++ i)
if (i < rev[i]) swap(a[i], a[rev[i]]);
for (int i = 1; i < n; i <<= 1) {
for (int j = 0; j < n; j += i << 1)
for (int k = 0; k < i; ++ k) {
int x = 1ll * (type ? inv[Log[i]][k] : omega[Log[i]][k]) * a[i + j + k] % mod;
a[i + j + k] = mns(a[j + k], x), add(a[j + k], x);
}
}
}
int f[3100000], g[3100000];
int main() {
int n = read(), m = read();
for (int i = 0; i <= n; ++ i) f[i] = read();
for (int i = 0; i <= m; ++ i) g[i] = read();
int lim = 1, k = 0;
while (lim < n + m + 1) lim <<= 1, ++ k;
for (int i = 2; i <= lim; ++ i) Log[i] = Log[i >> 1] = 1;
for (int i = 0; i <= k; ++ i) {
int org = qpow(G, (mod - 1) >> i), invorg = qpow(org, mod - 2);
omega[i][0] = inv[i][0] = 1;
for (int j = 1; j < 1 << i && j < lim; ++ j)
omega[i][j] = 1ll * omega[i][j - 1] * org % mod, inv[i][j] = 1ll * inv[i][j - 1] * invorg % mod;
}
for (int i = 2; i <= lim; ++ i) Log[i] = Log[i >> 1] + 1;
for (int i = 0; i < lim; ++ i) rev[i] = rev[i >> 1] >> 1 | (i & 1) << k - 1;
NTT(f, lim, 0), NTT(g, lim, 0);
for (int i = 0; i < lim; ++ i) f[i] = 1ll * f[i] * g[i] % mod;
NTT(f, lim, 1);
for (int i = 0; i < lim; ++ i) f[i] = 1ll * f[i] * qpow(lim, mod - 2) % mod;
for (int i = 0; i <= n + m; ++ i) printf("%d ", (int)(f[i] + mod) % mod);
return 0;
}
本文作者:zqs2020
本文链接:https://www.cnblogs.com/stinger/p/16413439.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步