Some Code Template Just for Fun.
#define PROBLEM "https://judge.yosupo.jp/problem/convolution_mod"
#include "../ModInt/Modint32.hpp"
#include "../Polynomial/Poly.hpp"
#include "../Template/Template.hpp"
int main() {
int n, m;
constexpr int mod = 998244353;
using Z = mint<mod>;
using poly = Poly<Z>;
std::cin >> n >> m;
std::vector<Z> a(n), b(m);
for (int i = 0; i < n; i++) std::cin >> a[i];
for (int i = 0; i < m; i++) std::cin >> b[i];
auto ans = poly(a) * poly(b);
for (int i = 0; i < (int)(ans.size()); i++)
std::cout << ans[i] << " \n"[i == (int)ans.size() - 1];
}
#line 1 "Verify/convolution.test.cpp"
#define PROBLEM "https://judge.yosupo.jp/problem/convolution_mod"
#line 2 "ModInt/Modint32.hpp"
#line 2 "Template/Template.hpp"
#include <bits/stdc++.h>
using i64 = std::int64_t;
#line 4 "ModInt/Modint32.hpp"
template <int mod>
struct mint {
int x;
mint() : x(0) {}
mint(int64_t y) : x(y >= 0 ? y % mod : (mod - (-y) % mod) % mod) {}
mint &operator+=(const mint &p) {
if ((x += p.x) >= mod) x -= mod;
return *this;
}
mint &operator-=(const mint &p) {
if ((x += mod - p.x) >= mod) x -= mod;
return *this;
}
mint &operator*=(const mint &p) {
x = (int)(1LL * x * p.x % mod);
return *this;
}
mint &operator/=(const mint &p) {
*this *= p.inverse();
return *this;
}
mint operator-() const { return mint(-x); }
mint operator+(const mint &p) const { return mint(*this) += p; }
mint operator-(const mint &p) const { return mint(*this) -= p; }
mint operator*(const mint &p) const { return mint(*this) *= p; }
mint operator/(const mint &p) const { return mint(*this) /= p; }
bool operator==(const mint &p) const { return x == p.x; }
bool operator!=(const mint &p) const { return x != p.x; }
mint inverse() const {
int a = x, b = mod, u = 1, v = 0, t;
while (b > 0) {
t = a / b;
std::swap(a -= t * b, b);
std::swap(u -= t * v, v);
}
return mint(u);
}
friend std::ostream &operator<<(std::ostream &os, const mint &p) {
return os << p.x;
}
friend std::istream &operator>>(std::istream &is, mint &a) {
int64_t t;
is >> t;
a = mint<mod>(t);
return (is);
}
int get() const { return x; }
static constexpr int get_mod() { return mod; }
};
#line 2 "Polynomial/Poly.hpp"
#line 2 "Polynomial/Ntt.hpp"
#line 1 "Template/Power.hpp"
template <class T>
T power(T a, int b) {
T res = 1;
for (; b; b /= 2, a *= a) {
if (b % 2) {
res *= a;
}
}
return res;
}
#line 5 "Polynomial/Ntt.hpp"
template <class Z>
struct NTT {
std::vector<int> rev;
std::vector<Z> roots{0, 1};
static constexpr int getRoot() {
auto _mod = Z::get_mod();
using u64 = uint64_t;
u64 ds[32] = {};
int idx = 0;
u64 m = _mod - 1;
for (u64 i = 2; i * i <= m; ++i) {
if (m % i == 0) {
ds[idx++] = i;
while (m % i == 0) m /= i;
}
}
if (m != 1) ds[idx++] = m;
int _pr = 2;
for (;;) {
int flg = 1;
for (int i = 0; i < idx; ++i) {
u64 a = _pr, b = (_mod - 1) / ds[i], r = 1;
for (; b; a = a * a % _mod, b /= 2) {
if (b % 2 == 1) r = r * a % _mod;
}
if (r == 1) {
flg = 0;
break;
}
}
if (flg == 1) break;
++_pr;
}
return _pr;
};
static constexpr int rt = getRoot();
void dft(std::vector<Z> &a) {
int n = a.size();
if (int(rev.size()) != n) {
int k = __builtin_ctz(n) - 1;
rev.resize(n);
for (int i = 0; i < n; i++) {
rev[i] = rev[i >> 1] >> 1 | (i & 1) << k;
}
}
for (int i = 0; i < n; i++) {
if (rev[i] < i) {
std::swap(a[i], a[rev[i]]);
}
}
if (int(roots.size()) < n) {
int k = __builtin_ctz(roots.size());
roots.resize(n);
while ((1 << k) < n) {
Z e = power(Z(rt), (Z::get_mod() - 1) >> (k + 1));
for (int i = 1 << (k - 1); i < (1 << k); i++) {
roots[2 * i] = roots[i];
roots[2 * i + 1] = roots[i] * e;
}
k++;
}
}
for (int k = 1; k < n; k *= 2) {
for (int i = 0; i < n; i += 2 * k) {
for (int j = 0; j < k; j++) {
Z u = a[i + j];
Z v = a[i + j + k] * roots[k + j];
a[i + j] = u + v;
a[i + j + k] = u - v;
}
}
}
}
void idft(std::vector<Z> &a) {
int n = a.size();
reverse(a.begin() + 1, a.end());
dft(a);
Z inv = (1 - Z::get_mod()) / n;
for (int i = 0; i < n; i++) {
a[i] *= inv;
}
}
std::vector<Z> multiply(std::vector<Z> a, std::vector<Z> b) {
int sz = 1, tot = a.size() + b.size() - 1;
if (tot <= 20) {
std::vector<Z> ret(tot);
for (size_t i = 0; i < a.size(); i++)
for (size_t j = 0; j < b.size(); j++) ret[i + j] += a[i] * b[j];
return ret;
}
while (sz < tot) {
sz *= 2;
}
a.resize(sz), b.resize(sz);
dft(a), dft(b);
for (int i = 0; i < sz; ++i) {
a[i] = a[i] * b[i];
}
idft(a);
a.resize(tot);
return a;
}
};
#line 4 "Polynomial/Poly.hpp"
template <class Z>
struct Poly {
std::vector<Z> a;
Poly() {}
Poly(int sz, Z val) { a.assign(sz, val); }
Poly(const std::vector<Z> &a) : a(a) {}
Poly(const std::initializer_list<Z> &a) : a(a) {}
int size() const { return a.size(); }
void resize(int n) { a.resize(n); }
Z operator[](int idx) const {
if (idx < size()) {
return a[idx];
} else {
return 0;
}
}
Z &operator[](int idx) { return a[idx]; }
Poly mulxk(int k) const {
auto b = a;
b.insert(b.begin(), k, 0);
return Poly(b);
}
Poly modxk(int k) const {
k = std::min(k, size());
return Poly(std::vector<Z>(a.begin(), a.begin() + k));
}
Poly divxk(int k) const {
if (size() <= k) {
return Poly();
}
return Poly(std::vector<Z>(a.begin() + k, a.end()));
}
friend Poly operator+(const Poly &a, const Poly &b) {
std::vector<Z> res(std::max(a.size(), b.size()));
for (int i = 0; i < int(res.size()); i++) {
res[i] = a[i] + b[i];
}
return Poly(res);
}
friend Poly operator-(const Poly &a, const Poly &b) {
std::vector<Z> res(std::max(a.size(), b.size()));
for (int i = 0; i < int(res.size()); i++) {
res[i] = a[i] - b[i];
}
return Poly(res);
}
friend Poly operator*(Poly a, Poly b) {
if (a.size() == 0 || b.size() == 0) {
return Poly();
}
static NTT<Z> ntt;
return ntt.multiply(a.a, b.a);
}
friend Poly operator*(Z a, Poly b) {
for (int i = 0; i < int(b.size()); i++) {
b[i] *= a;
}
return b;
}
friend Poly operator*(Poly a, Z b) {
for (int i = 0; i < int(a.size()); i++) {
a[i] *= b;
}
return a;
}
Poly &operator+=(Poly b) { return (*this) = (*this) + b; }
Poly &operator-=(Poly b) { return (*this) = (*this) - b; }
Poly &operator*=(Poly b) { return (*this) = (*this) * b; }
Poly operator/(const Poly &r) const { return Poly(this->a) /= r; }
Poly rev(int deg = -1) const {
Poly ret(this->a);
if (deg != -1) ret.a.resize(deg, Z(0));
reverse(begin(ret.a), end(ret.a));
return ret;
}
Poly &operator/=(const Poly &r) {
if (this->size() < r.size()) {
this->a.clear();
return *this;
}
int n = this->size() - r.size() + 1;
return *this = (rev().modxk(n) * r.rev().inv(n)).modxk(n).rev(n);
}
Poly deriv() const {
if (a.empty()) {
return Poly();
}
std::vector<Z> res(size() - 1);
for (int i = 0; i < size() - 1; ++i) {
res[i] = Z(i + 1) * a[i + 1];
}
return Poly(res);
}
Poly integr() const {
std::vector<Z> res(size() + 1);
for (int i = 0; i < size(); ++i) {
res[i + 1] = a[i] / (i + 1);
}
return Poly(res);
}
Poly inv(int m) const {
Poly x{a[0].inverse()};
int k = 1;
while (k < m) {
k *= 2;
x = (x * (Poly{2} - modxk(k) * x)).modxk(k);
}
return x.modxk(m);
}
Poly log(int m) const { return (deriv() * inv(m)).integr().modxk(m); }
Poly exp(int m) const {
Poly x{1};
int k = 1;
while (k < m) {
k *= 2;
x = (x * (Poly{1} - x.log(k) + modxk(k))).modxk(k);
}
return x.modxk(m);
}
Poly pow(int k, int m) const {
int i = 0;
while (i < size() && a[i].get() == 0) {
i++;
}
if (i == size() || 1LL * i * k >= m) {
return Poly(std::vector<Z>(m));
}
Z v = a[i];
auto f = divxk(i) * v.inverse();
return (f.log(m - i * k) * k).exp(m - i * k).mulxk(i * k) * power(v, k);
}
Poly sqrt(int m) const {
Poly x{1};
int k = 1;
while (k < m) {
k *= 2;
x = (x + (modxk(k) * x.inv(k)).modxk(k)) * ((Z::get_mod() + 1) / 2);
}
return x.modxk(m);
}
Poly mulT(Poly b) const {
if (b.size() == 0) {
return Poly();
}
int n = b.size();
reverse(b.a.begin(), b.a.end());
return ((*this) * b).divxk(n - 1);
}
std::vector<Z> eval(std::vector<Z> x) const {
if (size() == 0) {
return std::vector<Z>(x.size(), 0);
}
const int n = std::max(int(x.size()), size());
std::vector<Poly> q(4 * n);
std::vector<Z> ans(x.size());
x.resize(n);
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
if (r - l == 1) {
q[p] = Poly{1, -x[l]};
} else {
int m = (l + r) / 2;
build(2 * p, l, m);
build(2 * p + 1, m, r);
q[p] = q[2 * p] * q[2 * p + 1];
}
};
build(1, 0, n);
std::function<void(int, int, int, const Poly &)> work =
[&](int p, int l, int r, const Poly &num) {
if (r - l == 1) {
if (l < int(ans.size())) {
ans[l] = num[0];
}
} else {
int m = (l + r) / 2;
work(2 * p, l, m, num.mulT(q[2 * p + 1]).modxk(m - l));
work(2 * p + 1, m, r, num.mulT(q[2 * p]).modxk(r - m));
}
};
work(1, 0, n, mulT(q[1].inv(n)));
return ans;
}
Poly inter(const Poly &y) const {
std::vector<Poly> Q(a.size() * 4), P(a.size() * 4);
std::function<void(int, int, int)> build = [&](int p, int l, int r) {
int m = (l + r) >> 1;
if (l == r) {
Q[p] = Poly{-a[m], Z(1)};
} else {
build(p * 2, l, m);
build(p * 2 + 1, m + 1, r);
Q[p] = Q[p * 2] * Q[p * 2 + 1];
}
};
build(1, 0, a.size() - 1);
Poly f;
f.a.resize((int)(Q[1].size()) - 1);
for (int i = 0; i + 1 < Q[1].size(); i += 1)
f[i] = (Q[1][i + 1] * (i + 1));
Poly g = f.eval(a);
std::function<void(int, int, int)> work = [&](int p, int l, int r) {
int m = (l + r) >> 1;
if (l == r) {
P[p].a.push_back(y[m] * power(g[m], Z::get_mod() - 2));
return;
}
work(p * 2, l, m);
work(p * 2 + 1, m + 1, r);
P[p].a.resize(r - l + 1);
Poly A = P[p * 2] * Q[p * 2 + 1];
Poly B = P[p * 2 + 1] * Q[p * 2];
for (int i = 0; i <= r - l; i++) P[p][i] = (A[i] + B[i]);
};
work(1, 0, a.size() - 1);
return P[1];
}
Poly shift(Z t, int m = -1) {
/*
input: y(0) , y(1) , ... , y(n-1)
output: y(t) , t(t+1) , ... ,y (t+m-1)
## m = -1 => m = n
*/
if (m == -1) m = this->size();
i64 T = t.get();
int k = (int)(this->size()) - 1;
T %= Z::get_mod();
if (T <= k) {
Poly ret(m, 0);
int ptr = 0;
for (i64 i = T; i <= k && ptr < m; i++) ret[ptr++] = a[i];
if (k + 1 < T + m) {
auto suf = shift(k + 1, m - ptr);
for (int i = k + 1; i < T + m; i++)
ret[ptr++] = suf[i - (k + 1)];
}
return ret;
}
if (T + m > Z::get_mod()) {
auto pref = shift(T, Z::get_mod() - T);
auto suf = shift(0, m - pref.size());
copy(begin(suf.a), end(suf.a), back_inserter(pref.a));
return pref;
}
Poly finv(k + 1, 1), d(k + 1, 0);
for (int i = 2; i <= k; i++) finv[k] *= i;
finv[k] = Z(1) / finv[k];
for (int i = k; i >= 1; i--) finv[i - 1] = finv[i] * i;
for (int i = 0; i <= k; i++) {
d[i] = finv[i] * finv[k - i] * a[i];
if ((k - i) & 1) d[i] = -d[i];
}
Poly h(m + k, 0);
for (int i = 0; i < m + k; i++) {
h[i] = Z(1) / (T - k + i);
}
auto dh = d * h;
Poly ret(m, 0);
Z cur = T;
for (int i = 1; i <= k; i++) cur *= T - i;
for (int i = 0; i < m; i++) {
ret[i] = cur * dh[k + i];
cur *= T + i + 1;
cur *= h[i];
}
return ret;
}
};
#line 6 "Verify/convolution.test.cpp"
int main() {
int n, m;
constexpr int mod = 998244353;
using Z = mint<mod>;
using poly = Poly<Z>;
std::cin >> n >> m;
std::vector<Z> a(n), b(m);
for (int i = 0; i < n; i++) std::cin >> a[i];
for (int i = 0; i < m; i++) std::cin >> b[i];
auto ans = poly(a) * poly(b);
for (int i = 0; i < (int)(ans.size()); i++)
std::cout << ans[i] << " \n"[i == (int)ans.size() - 1];
}