/**
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 i64 = int64_t;
using u64 = uint64_t;
using u128 = unsigned __int128;
constexpr u64 MOD = 2524775926340780033ULL; // p-1 = c*2^24, primitive root 3
constexpr u64 G = 3;
constexpr int MAXN = (1 << 21); // du cho n+m+1 <= 2e6
u64 power_plain(u64 a, u64 e, u64 mod){
a %= mod;
u64 res = 1;
while (e){
if (e & 1) res = (u128)res * a % mod;
a = (u128)a * a % mod;
e >>= 1;
}
return res;
}
struct Montgomery64 {
u64 mod, inv_mod, r2;
void set_mod(u64 m){
mod = m;
inv_mod = 1;
for (int i = 0; i < 6; i++) inv_mod *= 2 - mod * inv_mod;
u64 r = (u64)(((u128)1 << 64) % mod);
r2 = (u64)((u128)r * r % mod);
}
u64 reduce(u128 x) const {
u64 q = (u64)x * inv_mod;
u128 m = (u128)q * mod;
u64 y = (u64)((x - m) >> 64);
return (y >> 63) ? y + mod : y;
}
u64 to_mont(u64 a) const { return reduce((u128)a * r2); }
u64 from_mont(u64 a) const { return reduce((u128)a); }
u64 mul(u64 a, u64 b) const { return reduce((u128)a * b); }
u64 add(u64 a, u64 b) const { return a + b >= mod ? a + b - mod : a + b; }
u64 sub(u64 a, u64 b) const { return a >= b ? a - b : a + mod - b; }
};
Montgomery64 mt;
vector<u64> root, root_inv; // bang precompute, luu dang Montgomery
// build mot lan cho n = MAXN, dung chung cho moi bound <= MAXN
void precompute_root(int n){
root.resize(n); root_inv.resize(n);
root[1] = mt.to_mont(1);
root_inv[1] = mt.to_mont(1);
u64 g_inv = power_plain(G, MOD - 2, MOD);
for (int k = 2; k * 2 <= n; k <<= 1){
u64 w = power_plain(G, (MOD - 1) / (2 * k), MOD);
u64 w_inv = power_plain(g_inv, (MOD - 1) / (2 * k), MOD);
u64 w_mont = mt.to_mont(w);
u64 w_inv_mont = mt.to_mont(w_inv);
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);
}
}
}
void ntt_forward(vector<u64>& a){
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++){
u64 w = root[half + j];
u64 u = a[i + j];
u64 v = a[i + j + half];
a[i + j] = mt.add(u, v);
a[i + j + half] = mt.mul(mt.sub(u, v), w);
}
}
}
}
void ntt_inverse(vector<u64>& a){
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++){
u64 w = root_inv[half + j];
u64 u = a[i + j];
u64 v = mt.mul(a[i + j + half], w);
a[i + j] = mt.add(u, v);
a[i + j + half] = mt.sub(u, v);
}
}
}
u64 n_inv = mt.to_mont(power_plain((u64)n, MOD - 2, MOD));
for (auto& x : a) x = mt.mul(x, n_inv);
}
vector<u64> multiply_mod(const vector<i64>& a, const vector<i64>& b, int bound){
vector<u64> A(bound), B(bound);
up(i, 0, a.size()-1) A[i] = mt.to_mont(a[i] < 0 ? (u64)(a[i] + (i64)MOD) : (u64)a[i]);
up(i, 0, b.size()-1) B[i] = mt.to_mont(b[i] < 0 ? (u64)(b[i] + (i64)MOD) : (u64)b[i]);
ntt_forward(A);
ntt_forward(B);
up(i, 0, bound-1) A[i] = mt.mul(A[i], B[i]);
ntt_inverse(A);
up(i, 0, bound-1) A[i] = mt.from_mont(A[i]);
return A;
}
// |result| <= 1e18 < MOD/2
i64 to_signed(u64 x){
return (x > MOD / 2) ? (i64)x - (i64)MOD : (i64)x;
}
void solve(){
int n, m; cin >> n >> m;
vector<i64> 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<u64> raw = multiply_mod(a, b, bound);
up(i, 0, need-1) cout << to_signed(raw[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);
}
mt.set_mod(MOD);
precompute_root(MAXN); // build 1 lan, dung cho moi bound <= MAXN
int tt; cin >> tt;
while (tt--) solve();
}