#pragma GCC optimize("O3") #include #include #include #include using namespace std; const int MOD = 998244353; const int G = 3; // --- ????????? --- int power(int base, int exp) { int res = 1; base %= MOD; while (exp > 0) { if (exp % 2 == 1) res = (1LL * res * base) % MOD; base = (1LL * base * base) % MOD; exp /= 2; } return res; } int modInverse(int n) { return power(n, MOD - 2); } struct Comb { vector fac, ifac; void init(int n) { fac.assign(n + 1, 1); ifac.assign(n + 1, 1); for (int i = 1; i <= n; ++i) fac[i] = (1LL * fac[i - 1] * i) % MOD; ifac[n] = modInverse(fac[n]); for (int i = n; i >= 1; --i) ifac[i - 1] = (1LL * ifac[i] * i) % MOD; } int C(int n, int k) { if (k < 0 || k > n) return 0; return (1LL * fac[n] * ifac[k]) % MOD * ifac[n - k] % MOD; } } comb; // --- NTT ? ???????? --- void ntt(vector& a, bool invert) { int n = a.size(); for (int i = 1, j = 0; i < n; i++) { int bit = n >> 1; for (; j & bit; bit >>= 1) j ^= bit; j ^= bit; if (i < j) swap(a[i], a[j]); } for (int len = 2; len <= n; len <<= 1) { int wlen = power(G, (MOD - 1) / len); if (invert) wlen = modInverse(wlen); for (int i = 0; i < n; i += len) { int w = 1; for (int j = 0; j < len / 2; j++) { int u = a[i + j]; int v = (1LL * a[i + j + len / 2] * w) % MOD; a[i + j] = (u + v >= MOD ? u + v - MOD : u + v); a[i + j + len / 2] = (u - v < 0 ? u - v + MOD : u - v); w = (1LL * w * wlen) % MOD; } } } if (invert) { int n_inv = modInverse(n); for (int& x : a) x = (1LL * x * n_inv) % MOD; } } vector poly_mul(vector const& a, vector const& b) { if (a.empty() || b.empty()) return {}; vector fa(a.begin(), a.end()), fb(b.begin(), b.end()); int n = 1; while (n < a.size() + b.size()) n <<= 1; fa.resize(n, 0); fb.resize(n, 0); ntt(fa, false); ntt(fb, false); for (int i = 0; i < n; i++) fa[i] = (1LL * fa[i] * fb[i]) % MOD; ntt(fa, true); int sz = a.size() + b.size() - 1; fa.resize(sz); while (fa.size() > 1 && fa.back() == 0) fa.pop_back(); return fa; } vector poly_inv(vector a, int deg) { if (deg == 1) return {modInverse(a[0])}; vector res = poly_inv(a, (deg + 1) / 2); int n = 1; while (n < deg * 2) n <<= 1; vector fa(a.begin(), a.begin() + min((int)a.size(), deg)); fa.resize(n, 0); vector fres = res; fres.resize(n, 0); ntt(fa, false); ntt(fres, false); for (int i = 0; i < n; i++) { fres[i] = (2LL - 1LL * fa[i] * fres[i] % MOD + MOD) % MOD * fres[i] % MOD; } ntt(fres, true); fres.resize(deg); return fres; } void poly_div_mod(vector A, vector B, vector& Q, vector& R) { while (A.size() > 1 && A.back() == 0) A.pop_back(); while (B.size() > 1 && B.back() == 0) B.pop_back(); int n = A.size(), m = B.size(); if (n < m) { Q = {0}; R = A; return; } vector revA = A, revB = B; reverse(revA.begin(), revA.end()); reverse(revB.begin(), revB.end()); revB.resize(n - m + 1, 0); vector invB = poly_inv(revB, n - m + 1); Q = poly_mul(revA, invB); Q.resize(n - m + 1, 0); reverse(Q.begin(), Q.end()); vector QB = poly_mul(Q, B); R.resize(m - 1, 0); for (int i = 0; i < m - 1; i++) { R[i] = (A[i] - QB[i]) % MOD; if (R[i] < 0) R[i] += MOD; } while (R.size() > 1 && R.back() == 0) R.pop_back(); } // --- ???? (Multipoint Evaluation) --- vector> eval_tree_nodes; void build_tree(int node, int l, int r, const vector& X) { if (l == r) { eval_tree_nodes[node] = {(MOD - X[l]) % MOD, 1}; return; } int mid = (l + r) / 2; build_tree(2 * node, l, mid, X); build_tree(2 * node + 1, mid + 1, r, X); eval_tree_nodes[node] = poly_mul(eval_tree_nodes[2 * node], eval_tree_nodes[2 * node + 1]); } void eval_tree(int node, int l, int r, vector P, vector& res) { vector Q, R; poly_div_mod(P, eval_tree_nodes[node], Q, R); if (l == r) { res[l] = R.empty() ? 0 : R[0]; return; } int mid = (l + r) / 2; eval_tree(2 * node, l, mid, R, res); eval_tree(2 * node + 1, mid + 1, r, R, res); } // --- ?????? Holonomic BSGS --- struct PolyMat { vector V, U, S; }; PolyMat combine(const PolyMat& left, const PolyMat& right) { PolyMat res; res.V = poly_mul(left.V, right.V); res.U = poly_mul(left.U, right.U); vector p1 = poly_mul(left.V, right.S); vector p2 = poly_mul(left.S, right.U); res.S.resize(max(p1.size(), p2.size()), 0); for (size_t i = 0; i < p1.size(); i++) res.S[i] = (res.S[i] + p1[i]) % MOD; for (size_t i = 0; i < p2.size(); i++) res.S[i] = (res.S[i] + p2[i]) % MOD; while (res.S.size() > 1 && res.S.back() == 0) res.S.pop_back(); return res; } vector build_poly(const vector>& factors) { vector res = {1}; for (auto& f : factors) { vector next_res(res.size() + 1, 0); for (size_t i = 0; i < res.size(); i++) { next_res[i] = (next_res[i] + 1LL * res[i] * f.first) % MOD; next_res[i + 1] = (next_res[i + 1] + 1LL * res[i] * f.second) % MOD; } res = next_res; } return res; } vector shift_poly(const vector& A, int c) { if (A.empty()) return {}; int n = A.size(); vector res(n, 0); vector c_pow(n, 1); for (int i = 1; i < n; i++) c_pow[i] = (1LL * c_pow[i - 1] * c) % MOD; for (int i = 0; i < n; i++) { for (int j = 0; j <= i; j++) { long long term = 1LL * A[i] * comb.C(i, j) % MOD; term = term * c_pow[i - j] % MOD; res[j] = (res[j] + term) % MOD; } } while (res.size() > 1 && res.back() == 0) res.pop_back(); return res; } long long H_base, W_base, N, P; vector U_poly, V_poly; PolyMat B_base(int i) { PolyMat res; res.V = shift_poly(V_poly, i); res.U = shift_poly(U_poly, i); res.S = res.U; // S ?? M(i) ???????? return res; } PolyMat build_block(int l, int r) { if (l == r) return B_base(l); int mid = l + (r - l) / 2; PolyMat right = build_block(l, mid); PolyMat left = build_block(mid + 1, r); return combine(left, right); // M_left * M_right } long long ceil_div(long long a, long long b) { return a >= 0 ? (a + b - 1) / b : a / b; } long long floor_div(long long a, long long b) { return a >= 0 ? a / b : (a - b + 1) / b; } // ????????????? long long solve_seq(long long offset) { long long H = H_base + offset; long long W = W_base + offset; long long min_kP = max(-H, -W); long long max_kP = min(N - H, N - W); if (min_kP > max_kP) return 0; long long L = ceil_div(min_kP, P); long long R = floor_div(max_kP, P); if (L > R) return 0; long long M = R - L + 1; H = H + L * P; W = W + L * P; // ????????(M??)??? O(M) ??????????????? if (M <= 1000) { long long sum = 0; for (int i = 0; i < M; i++) { sum = (sum + 1LL * comb.C(N, H + i * P) * comb.C(N, W + i * P)) % MOD; } return sum; } // --- ?? O(\sqrt{N} \log^2 N) ??????? --- int S = max(1, (int)sqrt(M)); int K = M / S; vector> U_factors, V_factors; for (int j = 1; j <= P; j++) { U_factors.push_back({((N - H + j) % MOD + MOD) % MOD, (MOD - P % MOD) % MOD}); U_factors.push_back({((N - W + j) % MOD + MOD) % MOD, (MOD - P % MOD) % MOD}); V_factors.push_back({((H - P + j) % MOD + MOD) % MOD, P % MOD}); V_factors.push_back({((W - P + j) % MOD + MOD) % MOD, P % MOD}); } U_poly = build_poly(U_factors); V_poly = build_poly(V_factors); PolyMat B = build_block(0, S - 1); vector X_pts(K); for (int j = 0; j < K; j++) X_pts[j] = (1LL * j * S + 1) % MOD; eval_tree_nodes.assign(4 * K, vector()); build_tree(1, 0, K - 1, X_pts); vector eval_V(K), eval_U(K), eval_S(K); eval_tree(1, 0, K - 1, B.V, eval_V); eval_tree(1, 0, K - 1, B.U, eval_U); eval_tree(1, 0, K - 1, B.S, eval_S); long long true_S_val = 1LL * comb.C(N, H) * comb.C(N, W) % MOD; long long true_c_val = true_S_val; // ???? (?? Giant-Step) for (int j = 0; j < K; j++) { int V_val = eval_V[j], U_val = eval_U[j], S_val = eval_S[j]; long long next_S = (1LL * V_val * true_S_val + 1LL * S_val * true_c_val) % MOD; long long next_c = (1LL * U_val * true_c_val) % MOD; long long inv_V = modInverse(V_val); true_S_val = next_S * inv_V % MOD; true_c_val = next_c * inv_V % MOD; } // ???? (?? Baby-Step) for (int i = K * S + 1; i < M; i++) { long long term = 1LL * comb.C(N, H + i * P) * comb.C(N, W + i * P) % MOD; true_S_val = (true_S_val + term) % MOD; } return true_S_val; } int main() { ios::sync_with_stdio(false); cin.tie(nullptr); long long H_in, W_in, K_in; if (!(cin >> H_in >> W_in >> K_in)) return 0; H_base = H_in - 1; W_base = W_in - 1; N = H_base + W_base; long long d = K_in / 2; P = 2 * d + 2; comb.init(N); if (d == 0) { cout << comb.C(N, H_base) << '\n'; return 0; } if (d >= min(H_base, W_base)) { long long all = comb.C(N, H_base); cout << all * all % MOD << '\n'; return 0; } long long ans = (solve_seq(0) - solve_seq(d + 1) + MOD) % MOD; cout << ans << '\n'; return 0; }