注意,下面文字中用 表示 n 次單位根(而不是用 )。
求多項式 在 處的點值( 是 2 的冪)。
將 分成按照次數奇偶分成兩部分。令:
則有 。
帶入單位根。
如此可以分治成兩個規模減半的子問題,然後 歸併。
反過來,從點值如何求回原多項式?令:
即 F 的 FFT 算出來的點值組成一個多項式。我們注意到將 帶入 A:
計算後面的 ,分類討論 和 。
對於 ,則 ,上式等於 。
對於 ,則可以應用等比數列求和:
綜上:
所以逆 FFT 相當於將單位根的逆元帶進去算,做類似 FFT 的步驟,最後再除以 n。
實現上。分治可以用倍增代替。分治按照次數奇偶劃分,對於 次項係數 ,它在分治的第 層被劃分到 當且僅當 的第 位是 ,分到 同理,總體來看,也就相當於將係數的二進制表示倒過來。
可以 遞推預處理一個數倒過來的值。令 表示 倒過來的值,容易從 遞推過來。
實現(yosupo 提交記錄):
template <typename mint>
std::vector<mint> ntt(std::vector<mint> a, const std::vector<mint> &w)
{
u32 n = a.size();
u32 log = w.size() - 1;
std::vector<u32> r(n);
r[0] = 0;
for (u32 i = 1; i < n; i++) r[i] = r[i / 2] / 2 + ((i & 1) << (log - 1));
for (u32 i = 0; i < n; i++) {
if (i < r[i]) std::swap(a[i], a[r[i]]);
}
for (u32 i = 0; i < log; i++) {
u32 len = 1u << i;
for (u32 j = 0; j < n; j += len * 2) {
mint wi = 1;
for (u32 k = j, end = j + len; k < end; k++) {
mint g = a[k];
mint h = wi * a[k + len];
a[k] = g + h;
a[k + len] = g - h;
wi *= w[i + 1];
}
}
}
return a;
}
template <typename mint, mint pr = 3>
std::pair<std::vector<mint>, std::vector<mint>> prepare_pr(u32 log)
{
std::vector<mint> w(log + 1), iw(log + 1);
w[log] = pr.pow((mint::UM - 1) >> log);
iw[log] = w[log].inv();
for (int i = log; i--; ) {
w[i] = w[i + 1] * w[i + 1];
iw[i] = iw[i + 1] * iw[i + 1];
}
return {w, iw};
}
template <typename mint, mint pr = 3>
std::vector<mint> convolution(std::vector<mint> a, std::vector<mint> b)
{
u32 n = a.size();
u32 m = b.size();
u32 log = 0;
while ((1u << log) < n + m - 1) log++;
u32 size = 1u << log;
a.resize(size);
b.resize(size);
auto [w, iw] = prepare_pr<mint, pr>(log);
a = ntt(std::move(a), w);
b = ntt(std::move(b), w);
for (u32 i = 0; i < size; i++) a[i] *= b[i];
a = ntt(std::move(a), iw);
mint isize = mint{size}.inv();
for (auto &i : a) i *= isize;
a.resize(n + m - 1);
return a;
}