多项式乘法 NTT
前置知识
原根
若存在 且使得 的最小正整数为 ,则 为 的原根
以下讨论中选择 为质数
次单位根
一些性质
主过程
多项式乘法逆 Inv
倍增处理,假设已经求出 意义下的逆元记为
因为 最低系数不为零的项次数为 平方后 最低系数不为零的项次数为 所以
两边同时乘
多项式对数函数 Ln
先微分再积分
多项式指数函数 Exp
设 其中 为多项式
求 的根,考虑牛顿迭代法
假设已经求出
因为 最低系数不为零的项次数为 平方后 最低系数不为零的项次数为 所以
多项式快速幂 Pow
/// @tags: NTT Polynomial
#include <algorithm>
#include <cctype>
#include <cstdio>
#include <cstring>
typedef long long ll;
int const N = 1 << 18, P = 998244353, g = 3, ig = 332748118, inv2 = 499122177;
template <typename T>
inline T &read(T &x) {
x = 0;
bool F = false;
short ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') F = true;
ch = getchar();
}
while (isdigit(ch)) x = x * 10ll % P + (ch ^ '0'), ch = getchar();
if (F) x = -x;
return x;
}
template <typename T>
inline T &readmod(T &x) {
x = 0;
bool F = false;
short ch = getchar();
while (!isdigit(ch)) {
if (ch == '-') F = true;
ch = getchar();
}
while (isdigit(ch)) x = 1ll * x * 10 % P + (ch ^ '0'), ch = getchar();
if (F) x = -x;
return x;
}
class Polynomial {
private:
int F[N];
static int Cvt[N << 2];
public:
void NTT(bool const typ, int const n);
void inv(Polynomial &Res, int const n) const;
void ln(Polynomial &Res, int const n) const;
void exp(Polynomial &Res, int const n) const;
void pow(Polynomial &Res, int const n, int const k) const;
void sqrt(Polynomial &res, int const n) const;
void div(Polynomial &Q, Polynomial &R, Polynomial const &D, int const n,
int const m) const;
int *operator&() { return F; }
int &operator[](int index) { return F[index]; };
int const &operator[](int index) const { return F[index]; };
inline void pre(int n) {
for (int i = 1, len = 2; len <= n; ++i, len <<= 1)
for (int j = 1, *const cvt = Cvt + len - 1; j < len; ++j)
cvt[j] = cvt[j >> 1] >> 1 | ((j & 1) << (i - 1));
}
inline void clear(int n) {
int maxl = 1;
while (maxl < n) maxl <<= 1;
memset(F, 0, sizeof(int) * maxl);
}
} F, G;
int n, m;
int Polynomial::Cvt[N << 2];
inline ll qpow(ll base, int exp) {
ll Res = 1;
while (exp) {
if (exp & 1) Res = Res * base % P;
base = base * base % P;
exp >>= 1;
}
return Res;
}
/// @param typ 正/逆向 @param n 项数 必须是2的整数次幂
inline void Polynomial::NTT(bool const typ, int const n) {
for (int i = 1, *const cvt = Cvt + n - 1; i < n; ++i)
if (i < cvt[i]) std::swap(F[i], F[cvt[i]]);
for (int i = 2; i <= n; i <<= 1) {
int mid = i >> 1, wn = qpow(typ ? g : ig, (P - 1) / i);
for (int j = 0; j < n; j += i) {
ll wk = 1;
for (int k = 0; k < mid; ++k, (wk *= wn) %= P) {
ll t = wk * F[j + k + mid] % P;
if ((F[j + k + mid] = F[j + k] - t) < 0) F[j + k + mid] += P;
if ((F[j + k] += t) >= P) F[j + k] -= P;
}
}
}
if (!typ) {
ll inv = qpow(n, P - 2);
for (int i = 0; i < n; ++i) F[i] = inv * F[i] % P;
}
}
/// @param Res 应清空 @param n 模的次数(项数)
inline void Polynomial::inv(Polynomial &Res, int const n) const {
static Polynomial tmp;
if (n == 1) return Res[0] = qpow(F[0], P - 2), void();
inv(Res, (n + 1) >> 1);
int maxl = 1;
while (maxl < n << 1) maxl <<= 1; // n - 1 次多项式卷 ⌈n / 2⌉ - 1 次多项式
tmp.clear(n << 1);
memcpy(&tmp, F, sizeof(int) * n);
tmp.NTT(true, maxl), Res.NTT(true, maxl);
for (int i = 0; i < maxl; ++i)
Res[i] = static_cast<ll>(Res[i]) *
((2ll - static_cast<ll>(Res[i]) * tmp[i] % P + P) % P) % P;
Res.NTT(false, maxl);
for (int i = n; i < maxl; ++i) Res[i] = 0; // mod x^n
}
inline void Polynomial::ln(Polynomial &Res, int const n) const {
static Polynomial df, invf;
df.clear(n << 1), invf.clear(n << 1);
int maxl = 1;
while (maxl < n << 1) maxl <<= 1;
for (int i = 0; i + 1 < n; ++i) df[i] = static_cast<ll>(F[i + 1]) * (i + 1) % P;
inv(invf, n);
invf.NTT(true, maxl), df.NTT(true, maxl);
for (int i = 0; i < maxl; ++i) Res[i] = static_cast<ll>(df[i]) * invf[i] % P;
Res.NTT(false, maxl);
for (int i = n - 1; i; --i) Res[i] = static_cast<ll>(Res[i - 1]) * qpow(i, P - 2) % P;
Res[0] = 0;
}
inline void Polynomial::exp(Polynomial &Res, int const n) const {
static Polynomial lnres;
if (n == 1) { return Res[0] = 1, void(); }
exp(Res, (n + 1) >> 1);
Res.ln(lnres, n);
int maxl = 1;
while (maxl < n << 1) maxl <<= 1;
for (int i = 0; i < n; ++i)
if ((lnres[i] = F[i] - lnres[i]) < 0) lnres[i] += P;
++lnres[0];
if (lnres[0] >= P) lnres[0] -= P;
lnres.NTT(true, maxl), Res.NTT(true, maxl);
for (int i = 0; i < maxl; ++i) Res[i] = static_cast<ll>(Res[i]) * lnres[i] % P;
Res.NTT(false, maxl);
for (int i = n; i < maxl; ++i) Res[i] = 0;
}
inline void Polynomial::pow(Polynomial &Res, int const n, int const k) const {
static Polynomial tmp;
ln(tmp, n);
for (int i = 0; i < n; ++i) tmp[i] = 1ll * tmp[i] * k % P;
tmp.exp(Res, n);
}
/// @param Q Quotient @param R Remainder @param D divider
inline void Polynomial::div(Polynomial &Q, Polynomial &R, Polynomial const &D,
int const n, int const m) const {
static Polynomial Dr, iDr, Fr;
for (int i = 0; i <= m; ++i) Dr[i] = D[m - i];
for (int i = 0; i <= n; ++i) Fr[i] = F[n - i];
Dr.inv(iDr, n - m + 1);
int maxl = 1;
while (maxl <= n + m) maxl <<= 1;
iDr.NTT(true, maxl << 1), Fr.NTT(true, maxl << 1);
for (int i = 0; i < maxl << 1; ++i) Q[i] = 1ll * iDr[i] * Fr[i] % P;
Q.NTT(false, maxl << 1);
for (int i = n - m + 1; i < maxl << 1; ++i) Q[i] = 0;
std::reverse(&Q, &Q + n - m + 1);
std::reverse(&Dr, &Dr + m + 1);
Q.NTT(true, maxl << 1), Dr.NTT(true, maxl << 1);
for (int i = 0; i < maxl << 1; ++i) R[i] = 1ll * Q[i] * Dr[i] % P;
R.NTT(false, maxl << 1), Q.NTT(false, maxl << 1);
for (int i = 0; i < m; ++i)
if ((R[i] = F[i] - R[i]) < 0) R[i] += P;
}
/// @param Res 应清空 @param n 模的次数(项数)
inline void Polynomial::sqrt(Polynomial &Res, int const n) const {
static Polynomial Resinv, copyF;
if (n == 1) { return Res[0] = 1, void(); }
sqrt(Res, (n + 1) >> 1);
for (int i = 0; i <= n << 1; ++i) Resinv[i] = 0;
Res.inv(Resinv, n);
int maxl = 1;
while (maxl < n << 1) maxl <<= 1;
memcpy(©F, F, sizeof(int) * n);
for (int i = n; i < maxl; ++i) copyF[i] = 0;
copyF.NTT(true, maxl), Resinv.NTT(true, maxl), Res.NTT(true, maxl);
for (int i = 0; i < maxl; ++i)
/// @warning P 过大时可能会炸 ↓
if ((Res[i] = 1ll * inv2 * (Res[i] + 1ll * copyF[i] * Resinv[i] % P) % P) >= P)
Res[i] -= P;
Res.NTT(false, maxl);
for (int i = n; i < maxl; ++i) Res[i] = 0;
}
int main() {
#ifndef ONLINE_JUDGE
#ifdef LOCAL
freopen("/tmp/CodeTmp/testdata.in", "r", stdin);
freopen("/tmp/CodeTmp/testdata.out", "w", stdout);
#else
freopen("Polynomial.in", "r", stdin);
freopen("Polynomial.out", "w", stdout);
#endif
#endif
read(n);
for (int i = 0; i < n; ++i) read(F[i]);
F.pre(n << 2);
F.sqrt(G, n);
for (int i = 0; i < n; ++i) printf("%d ", G[i]);
return 0;
}