#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define FOR(i, a, b) for (int i = (a), i##_len = (b); i < i##_len; i++)
#define ALL(x) (x).begin(), (x).end()  // sortなどの引数を省略したい
#ifdef _DEBUG
#define PRE_COMMAND             \
    std::cin.rdbuf(in.rdbuf()); \
    std::cout << fixed << setprecision(15);
#else
#define PRE_COMMAND cout << fixed << setprecision(15);
#endif
const double EPS = 1e-10, PI = acos(-1.0);
template <class T> auto MAX(T& seq) {
    return *max_element(seq.begin(), seq.end());
}
template <class T> auto MIN(T& seq) {
    return *min_element(seq.begin(), seq.end());
}
template <class T> auto SUM(T& seq) {
    T temp{0};
    auto& temp2 = temp[0];
    return accumulate(seq.begin(), seq.end(), temp2);
}
template <class T> void SORT(T& seq) { sort(seq.begin(), seq.end()); }
template <class T, class S> void SORT(T& seq, S& sort_order) {
    sort(seq.begin(), seq.end(), sort_order);
}
template <class T> void SORTR(vector<T>& seq) {
    sort(seq.begin(), seq.end(), greater<T>());
}
template <class T, class S, class R> long long pow(T n_0, S k_0, R mod_0) {
    long long n = n_0 % mod_0, k = k_0, mod = mod_0, now = 1;
    while (k) {
        if (k & 1) now = (now * n) % mod;
        n = (n * n) % mod;
        k >>= 1;
    }
    return now;
}
template <typename T> istream& operator>>(istream& is, vector<T>& vec) {
    for (T& x : vec) is >> x;
    return is;
}
struct ntt {
    using ll = long long;
    using vl = vector<ll>;
    array<vl, 2> exp_list{(vl)(1), (vl)(1)};
    vl result;
    int s, h, n;
    ll r = 3, mod, ninv;
    ntt(vl& x_pre, vl& y_pre, ll mod_pre) {
        s = (int)(x_pre.size() + y_pre.size()) - 1, h = 1, n = 2, mod = mod_pre;
        vl x = x_pre, y = y_pre;
        for (; n < s; n *= 2, h++) {}
        ninv = pow(n, mod - 2, mod);
        x.resize(n, 0);
        y.resize(n, 0);
        convolve(x, y);
    }
    // 高速フーリエ変換を行う、inv=trueで逆変換
    void ntt_calc(vl& x, bool inv = false) {
        ll now = 1;
        // 処理前の並び替え
        for (int i = 0; i < n; i++) {
            int j = 0;
            for (int k = 0; k < h; k++) { j |= ((i >> k) & 1) << (h - 1 - k); }
            if (i < j) { swap(x[i], x[j]); }
        }
        ll e = pow(r, (mod - 1) / n, mod);
        if (inv) e = pow(e, mod - 2, mod);
        if ((int)exp_list[inv].size() != n) {
            exp_list[inv].assign(n, 0);
            for (int i = 0; i < n; i++, now = now * e % mod) {
                exp_list[inv][i] = now;
            }
        }
        // 各ステップについて処理
        for (int b = 1; b < n; b *= 2) {
            // 各ブロックのj個目
            int m = n / b;
            const int m2 = 2 * b;
            ll w, u, t;
            for (int j = 0; j < b; j++) {
                w = exp_list[inv][j * m >> 1];
                // kを先頭とするブロックに対する処理
                for (int k = j; k < n; k += m2) {
                    u = x[k], t = x[k + b] * w % mod;
                    x[k] = u + t;
                    if (x[k] >= mod) x[k] -= mod;
                    x[k + b] = u - t;
                    if (x[k + b] < 0) x[k + b] += mod;
                }
            }
        }
        if (inv) {
            for (int i = 0; i < n; i++) { x[i] = x[i] * ninv % mod; }
        }
    }
    // aとbの畳み込みを計算
    void convolve(vl& x, vl& y) {
        ntt_calc(x);
        ntt_calc(y);
        for (int i = 0; i < (int)x.size(); i++) { x[i] = x[i] * y[i] % mod; }
        ntt_calc(x, true);
        x.resize(s);
        result = x;
    }
};

// 負の数にも対応した mod (a = -11 とかでも OK)
inline long long mod(long long a, long long m) {
    long long res = a % m;
    if (res < 0) res += m;
    return res;
}

// 拡張 Euclid の互除法
long long extGCD(long long a, long long b, long long& p, long long& q) {
    if (b == 0) {
        p = 1;
        q = 0;
        return a;
    }
    long long d = extGCD(b, a % b, q, p);
    q -= a / b * p;
    return d;
}

// 逆元計算 (ここでは a と m が互いに素であることが必要)
long long modinv(long long a, long long m) {
    long long x, y;
    extGCD(a, m, x, y);
    return mod(x, m);  // 気持ち的には x % m だが、x が負かもしれないので
}

// Garner のアルゴリズム, x%MOD, LCM%MOD を求める (m
// は互いに素でなければならない) for each step, we solve "coeffs[k] * t[k] +
// constants[k] = b[k] (mod. m[k])"
//      coeffs[k] = m[0]m[1]...m[k-1]
//      constants[k] = t[0] + t[1]m[0] + ... + t[k-1]m[0]m[1]...m[k-2]
long long Garner(vector<long long> b, vector<long long> m, long long MOD) {
    m.push_back(MOD);  // banpei
    vector<long long> coeffs((int)m.size(), 1);
    vector<long long> constants((int)m.size(), 0);
    for (int k = 0; k < (int)b.size(); ++k) {
        long long t =
            mod((b[k] - constants[k]) * modinv(coeffs[k], m[k]), m[k]);
        for (int i = k + 1; i < (int)m.size(); ++i) {
            (constants[i] += t * coeffs[i]) %= m[i];
            (coeffs[i] *= m[k]) %= m[i];
        }
    }
    return constants.back();
}

vector<long long> calc_ntt(vector<long long> x, vector<long long> y,
                           long long mod) {
    ntt ntt1(x, y, 167772161);
    ntt ntt2(x, y, 469762049);
    ntt ntt3(x, y, 1224736769);
    vector<long long> r1 = ntt1.result, r2 = ntt2.result, r3 = ntt3.result;
    int n = (int)r1.size();
    vector<long long> ret(n), mod_v = {167772161, 469762049, 1224736769};
    for (int i = 0; i < n; i++) {
        ret[i] = Garner({r1[i], r2[i], r3[i]}, mod_v, mod);
    }
    return ret;
}

int main() {
    PRE_COMMAND
    int n, m;
    cin >> n >> m;
    vector<ll> a(n), b(m);
    cin >> a >> b;
    vector<ll> ans = calc_ntt(a, b, 998244353);
    n = (int)ans.size();
    for (int i = 0; i < n; i++) {
        cout << ans[i];
        if (i != n - 1) {
            cout << " ";
        } else {
            cout << endl;
        }
    }
}

Measured memory_bytes: 126116