/**
* Đầu vào: Hai đa thức có độ dài n, m. 1 <= n, m <= 1e6-1 thể hiện bậc cao nhất của đa thức là n+1, m+1
Hệ số hạng tử nằm trong khoảng [-1e9; 1e9]
* Đầu ra: Đa thức kết quả ở dạng đầy đủ có giá trị trong khoảng [-1e24; 1e24], không mod
**/
#include <bits/stdc++.h>
#define up(i,a,b) for (int i = (int)a; i <= (int)b; i++)
using namespace std;
using i32 = int32_t;
using i64 = int64_t;
using u32 = uint32_t;
using u64 = uint64_t;
using i128 = __int128;
using u128 = unsigned __int128;
// ================= Fast IO (USACO style) =================
const int BUF_SZ = 1 << 20;
inline namespace Input {
char buf[BUF_SZ];
int pos, len;
inline char next_char() {
if (pos == len) {
pos = 0;
len = (int)fread(buf, 1, BUF_SZ, stdin);
if (!len) return EOF;
}
return buf[pos++];
}
inline i64 read_int() {
char ch;
while ((ch = next_char()) != '-' && !isdigit(ch));
bool neg = false;
if (ch == '-') { neg = true; ch = next_char(); }
i64 x = ch - '0';
while (isdigit(ch = next_char())) x = x * 10 + (ch - '0');
return neg ? -x : x;
}
}
inline namespace Output {
char buf[BUF_SZ];
int pos;
inline void flush_out() {
fwrite(buf, 1, pos, stdout);
pos = 0;
}
inline void write_char(char c) {
if (pos == BUF_SZ) flush_out();
buf[pos++] = c;
}
inline void write_int(i64 x) {
static char num_buf[24];
if (x < 0) { write_char('-'); x = -x; }
int len = 0;
do { num_buf[len++] = (char)('0' + (x % 10)); x /= 10; } while (x);
while (len) write_char(num_buf[--len]);
}
// in i128 co dau: tach 1 lan theo 10^18 (1 phep chia 128-bit), phan con lai dung u64
inline void write_i128(i128 x) {
if (pos + 48 > BUF_SZ) flush_out(); // dam bao du cho (dau + <=39 chu so)
u128 ux;
if (x < 0) { buf[pos++] = '-'; ux = (u128)0 - (u128)x; } else ux = (u128)x;
const u64 E18 = 1000000000000000000ULL;
u64 hi = (u64)(ux / E18);
u64 lo = (u64)(ux - (u128)hi * E18);
char t[24];
if (hi) {
// hi co the dai toi 21 chu so neu |x| ~ 1e39; voi bai nay hi < 4e7
int l = 0;
do { t[l++] = (char)('0' + hi % 10); hi /= 10; } while (hi);
while (l) buf[pos++] = t[--l];
for (int d = 17; d >= 0; d--) { buf[pos + d] = (char)('0' + lo % 10); lo /= 10; }
pos += 18; // lo dem du 18 chu so
} else {
int l = 0;
do { t[l++] = (char)('0' + lo % 10); lo /= 10; } while (lo);
while (l) buf[pos++] = t[--l];
}
}
void init_output() { int r = atexit(flush_out); (void)r; } // khong dung assert: -DNDEBUG se xoa mat atexit
}
// =========================================================
// 3 so nguyen to NTT-friendly chuan, cung primitive root g = 3 (deu < 2^31)
const u32 P1 = 998244353; // 119*2^23+1
const u32 P2 = 167772161; // 5*2^25+1
const u32 P3 = 469762049; // 7*2^26+1
const u32 R = 3;
const i128 M = (i128)P1 * P2 * P3;
const int MAXN = 1 << 21; // n+m+1 <= 2e6+1 <= 2^21
u64 power_plain(u64 a, u64 e, u64 mod){
a %= mod;
u64 res = 1;
while (e){
if (e & 1) res = res * a % mod;
a = a * a % mod;
e >>= 1;
}
return res;
}
// ================== MONTGOMERY (R = 2^32) ==================
struct Montgomery32 {
u32 mod, inv_mod, r2;
void set_mod(u32 m){
mod = m;
inv_mod = 1;
for (int i = 0; i < 5; i++) inv_mod *= 2 - mod * inv_mod;
u64 r = ((u64)1 << 32) % mod;
r2 = (u32)(r * r % mod);
}
// REDC: 0 <= x < mod * 2^32
u32 reduce(u64 x) const {
u32 q = (u32)x * inv_mod;
u64 m = (u64)q * mod;
u32 y = (u32)((x - m) >> 32);
return (y >> 31) ? y + mod : y;
}
u32 to_mont(u32 a) const { return reduce((u64)a * r2); }
u32 from_mont(u32 a) const { return reduce((u64)a); }
u32 mul(u32 a, u32 b) const { return reduce((u64)a * b); }
u32 add(u32 a, u32 b) const { u32 s = a + b; return s >= mod ? s - mod : s; }
u32 sub(u32 a, u32 b) const { return a >= b ? a - b : a + mod - b; }
};
// =============================================================
// Mot bo NTT cho 1 modulo: mt + bang root/root_inv (dang Montgomery)
struct NTT {
Montgomery32 mt;
u32 mod;
vector<u32> root, root_inv;
void init(u32 p, u32 g, int n){
mod = p;
mt.set_mod(p);
root.resize(n); root_inv.resize(n);
root[1] = root_inv[1] = mt.to_mont(1);
u32 g_inv = (u32)power_plain(g, p - 2, p);
for (int k = 2; k * 2 <= n; k <<= 1){
u32 w_mont = mt.to_mont((u32)power_plain(g, (p - 1) / (2 * k), p));
u32 w_inv_mont = mt.to_mont((u32)power_plain(g_inv, (p - 1) / (2 * k), p));
for (int j = k / 2; j < k; j++){
root[j * 2] = root[j];
root[j * 2 + 1] = mt.mul(root[j], w_mont);
root_inv[j * 2] = root_inv[j];
root_inv[j * 2 + 1] = mt.mul(root_inv[j], w_inv_mont);
}
}
}
// DIF (Gentleman-Sande): vao tu nhien -> ra bit-reversed
void forward(vector<u32>& a) const {
int n = a.size();
for (int len = n; len >= 2; len >>= 1){
int half = len / 2;
for (int i = 0; i < n; i += len){
for (int j = 0; j < half; j++){
u32 w = root[half + j];
u32 u = a[i + j], v = a[i + j + half];
a[i + j] = mt.add(u, v);
a[i + j + half] = mt.mul(mt.sub(u, v), w);
}
}
}
}
// DIT (Cooley-Tukey): vao bit-reversed -> ra tu nhien
void inverse(vector<u32>& a) const {
int n = a.size();
for (int len = 2; len <= n; len <<= 1){
int half = len / 2;
for (int i = 0; i < n; i += len){
for (int j = 0; j < half; j++){
u32 w = root_inv[half + j];
u32 u = a[i + j];
u32 v = mt.mul(a[i + j + half], w);
a[i + j] = mt.add(u, v);
a[i + j + half] = mt.sub(u, v);
}
}
}
u32 n_inv = mt.to_mont((u32)power_plain(n, mod - 2, mod));
for (auto& x : a) x = mt.mul(x, n_inv);
}
// tra ve he so tich modulo `mod`, o mien thuong (da from_mont)
vector<u32> multiply(const vector<i32>& a, const vector<i32>& b, int bound) const {
vector<u32> A(bound), B(bound);
// sửa multiply: đưa về [0, mod) đúng cách
up(i, 0, (int)a.size()-1){ i64 v = a[i] % (i64)mod; if (v < 0) v += mod; A[i] = mt.to_mont((u32)v); }
up(i, 0, (int)b.size()-1){ i64 v = b[i] % (i64)mod; if (v < 0) v += mod; B[i] = mt.to_mont((u32)v); }
forward(A);
forward(B);
up(i, 0, bound - 1) A[i] = mt.mul(A[i], B[i]);
inverse(A);
for (auto& x : A) x = mt.from_mont(x);
return A;
}
};
NTT ntt1, ntt2, ntt3;
u32 inv_P1_mod_P2, inv_P1P2_mod_P3;
// ================= Garner CRT: 3 modulus =================
// x = r1 + P1*t2 + P1*P2*t3, tra ve gia tri co dau (|x| < M/2)
i128 garner3(u32 r1, u32 r2, u32 r3){
// ghep (r1 mod P1) va (r2 mod P2): t2 = (r2 - r1) * P1^-1 mod P2
u64 t2 = (u64)(((i64)r2 - (i64)r1) % (i64)P2 + P2) % P2 * inv_P1_mod_P2 % P2;
u64 x12 = (u64)r1 + (u64)P1 * t2; // < P1*P2 ~ 1.7e17
// ghep x12 (mod P1*P2) voi (r3 mod P3): t3 = (r3 - x12) * (P1*P2)^-1 mod P3
u64 t3 = (u64)(((i64)r3 - (i64)(x12 % P3)) % (i64)P3 + P3) % P3 * inv_P1P2_mod_P3 % P3;
i128 x = (i128)x12 + (i128)P1 * P2 * t3;
if (x > M / 2) x -= M;
return x;
}
void solve(){
int n = (int)read_int(), m = (int)read_int();
vector<i32> a(n + 1), b(m + 1);
for (auto& x : a) x = (i32)read_int();
for (auto& x : b) x = (i32)read_int();
int need = (int)a.size() + (int)b.size() - 1;
int bound = 1;
while (bound < need) bound <<= 1;
vector<u32> r1 = ntt1.multiply(a, b, bound);
vector<u32> r2 = ntt2.multiply(a, b, bound);
vector<u32> r3 = ntt3.multiply(a, b, bound);
up(i, 0, need - 1){
write_i128(garner3(r1[i], r2[i], r3[i]));
write_char(i == need - 1 ? '\n' : ' ');
}
}
signed main(){
init_output();
#define Task "A"
if (fopen(Task".inp", "r")){
freopen(Task".inp", "r", stdin);
freopen(Task".out", "w", stdout);
}
ntt1.init(P1, R, MAXN);
ntt2.init(P2, R, MAXN);
ntt3.init(P3, R, MAXN);
inv_P1_mod_P2 = (u32)power_plain(P1 % P2, P2 - 2, P2);
inv_P1P2_mod_P3 = (u32)power_plain((u64)P1 * P2 % P3, P3 - 2, P3);
int tt = (int)read_int();
while (tt--) solve();
}