/**
Input:
Dòng 1: Số testcase
Dòng 2: Gồm hai số n, m
Dòng 3: Gồm n+1 hệ số của đa thức A bậc n (bao gồm cả hệ số của hạng tử x^0)
Dòng 4: Gồm m+1 hệ số của đa thức B bậc m (bao gồm cả hệ số của hạng tử x^0)
Output:
Với mỗi testcase in ra 1 dòng duy nhất gồm n+m+1 hệ số của đa thức tích C = A x B
Giới hạn:
1 <= n, m <= (1e6-1)
-1e6 <= Hệ số hạng tử <= 1e6
Tổng n+m trên tất cả các testcase không vượt quá 2e6
Ví dụ:
Input:
2
2 4
1 2 3
-4 1 2 5 -2
3 2
1 4 -4 -1
1 3 5
Output:
-4 -7 -8 12 14 11 -6
1 7 13 7 -23 -5
**/
#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;
// p = c*2^k + 1, deu < 2^31 (Montgomery32 can mod < 2^31 de bat dau am bang bit 31)
const u32 P1 = 2013265921, R1 = 31; // 15*2^27+1
const u32 P2 = 2130706433, R2 = 3; // 127*2^24+1
const i64 M = (i64)P1 * P2; // ~4.29e18 > 2*1e18
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);
up(i, 0, (int)a.size() - 1) A[i] = mt.to_mont(a[i] < 0 ? (u32)(a[i] + (i64)mod) : (u32)a[i]);
up(i, 0, (int)b.size() - 1) B[i] = mt.to_mont(b[i] < 0 ? (u32)(b[i] + (i64)mod) : (u32)b[i]);
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;
u32 inv_P1_mod_P2;
// ================= Garner CRT: 2 modulus =================
// x = r1 + P1 * t2, tra ve gia tri co dau (|x| < M/2)
i64 garner2(u32 r1, u32 r2){
u64 t2 = (u64)(r2 + (u64)P2 - r1) % P2 * inv_P1_mod_P2 % P2;
i64 x = (i64)r1 + (i64)P1 * (i64)t2;
if (x > M / 2) x -= M;
return x;
}
void solve(){
int n, m; cin >> n >> m;
vector<i32> a(n + 1), b(m + 1);
for (auto& x : a) cin >> x;
for (auto& x : b) cin >> x;
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);
up(i, 0, need - 1) cout << garner2(r1[i], r2[i]) << " \n"[i == need - 1];
}
signed main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
#define Task "A"
if (fopen(Task".inp", "r")){
freopen(Task".inp", "r", stdin);
freopen(Task".out", "w", stdout);
}
ntt1.init(P1, R1, MAXN);
ntt2.init(P2, R2, MAXN);
inv_P1_mod_P2 = (u32)power_plain(P1 % P2, P2 - 2, P2);
int tt; cin >> tt;
while (tt--) solve();
}