如何得到 次单位根
在 FNTT 中,我们并不是用原根来代替单位根,而是利用原根的性质,得到 次单位根。
考虑 FTT 中我们需要的单位根具有什么性质:
- 我们需要知道单位根 以及 。
- 每一次用当前的单位根 乘上 即可得到 。
- 进行 次操作之后恰好回到 。
- 这些单位根互不相同,否则点值会重复,插值会错误。
- 推导结论时需要用到的一些结论
若 模 意义下的阶恰好为 。根据阶的定义 互不相同,且 。那么我们设 ,容易验证,除 5.3 以外上述性质全部被满足。如何找到满足 模 意义下阶为 的数呢?
由原根的性质,若模 意义下有原根 ,则 的阶数为 。我们希望 的阶数恰好为 ,这样就能满足我们上述性质了。
也就是说,,容易知道,当 不是 的因子时无解;否则,取 ,就能得到 了。
所以,我们只需要知道 的一个原根,就能得到阶数为任意一个 的因子的数 ,进而用 代替单位根。
性质 5.3 由我们计算方法可以得到。
例如 时,,此时 的所有长度的 都存在 次单位根,我们就可以解决长度不超过 的多项式乘法了。
常用原根
,,
, ,
, .
后两个模数是使用 FFT 常数过大,没有模数,且结果不会爆 long long
时,对这两个模数取模做 NTT 可以加速卷积,但是中间结果可能会爆 long long
,需要开 __int128
。
模板代码
Sample Code(C++)
c++
const int N = 1 << 21;
const int mod = 998244353, g = 3;
using LL = long long;
auto qpow = [](LL a, LL b) {
LL res = 1;
for (; b; b >>= 1, a = a * a % mod)
if (b & 1) res = res * a % mod;
return res;
};
auto mul = [](LL *a, LL *b, LL *c, int n) {
static int tr[N];
for (int i = 0; i < n; ++i)
tr[i] = (tr[i >> 1] >> 1) | ((i & 1) ? n >> 1 : 0);
auto NTT = [](LL *a, int n, bool idft) {
for (int i = 0; i < n; ++i)
if (i < tr[i]) swap(a[i], a[tr[i]]);
for (int len = 2; len <= n; len <<= 1) {
int l = len >> 1;
LL chg = qpow(g, (mod - 1) / len);
if (idft) chg = qpow(chg, mod - 2);
for (int k = 0; k < n; k += len) {
LL rt = 1;
for (int j = k; j < k + l; ++j) {
LL tmp = a[j + l] * rt % mod;
a[j + l] = a[j] - tmp + mod;
if (a[j + l] >= mod) a[j + l] -= mod;
a[j] = a[j] + tmp;
if (a[j] >= mod) a[j] -= mod;
(rt *= chg) %= mod;
}
}
}
if (idft) {
LL inv_n = qpow(n, mod - 2);
for (int i = 0; i < n; ++i) (a[i] *= inv_n) %= mod;
}
};
NTT(a, n, false);
NTT(b, n, false);
for (int i = 0; i < n; ++i) c[i] = a[i] * b[i] % mod;
NTT(c, n, true);
};