#include using namespace std; //* ATCODER #include using namespace atcoder; typedef modint998244353 mint; //*/ /* BOOST MULTIPRECISION #include using namespace boost::multiprecision; //*/ typedef long long ll; #define rep(i, s, n) for (int i = (int)(s); i < (int)(n); i++) #define rrep(i, s, n) for (int i = (int)(n)-1; i >= (int)(s); i--) template bool chmin(T &a, const T &b) { if (a <= b) return false; a = b; return true; } template bool chmax(T &a, const T &b) { if (a >= b) return false; a = b; return true; } template T max(vector &a){ assert(!a.empty()); T ret = a[0]; for (int i=0; i<(int)a.size(); i++) chmax(ret, a[i]); return ret; } template T min(vector &a){ assert(!a.empty()); T ret = a[0]; for (int i=0; i<(int)a.size(); i++) chmin(ret, a[i]); return ret; } template T sum(vector &a){ T ret = 0; for (int i=0; i<(int)a.size(); i++) ret += a[i]; return ret; } // importbisect template int bisect_left(vector &X, T v){ return lower_bound(X.begin(), X.end(), v) - X.begin(); } template int bisect_right(vector &X, T v){ return upper_bound(X.begin(), X.end(), v) - X.begin(); } // ---- // defcomp template vector compress(vector &X) { vector vals = X; sort(vals.begin(), vals.end()); vals.erase(unique(vals.begin(), vals.end()), vals.end()); return vals; } // ----- //defmodfact const int COMinitMAX = 1100000; mint fact[COMinitMAX+1], factinv[COMinitMAX+1]; void modfact(){ fact[0] = 1; for (int i=1; i<=COMinitMAX; i++){ fact[i] = fact[i-1] * i; } factinv[COMinitMAX] = fact[COMinitMAX].inv(); for (int i=COMinitMAX-1; i>=0; i--){ factinv[i] = factinv[i+1] * (i+1); } } mint cmb(int a, int b){ if (a makediv(ll n){ vector ld, ud; for (ll i=1; i*i<=n; i++){ if (n%i == 0){ ld.push_back(i); if (i != n/i){ ud.push_back(n/i); } } } reverse(ud.begin(), ud.end()); ld.insert(ld.end(), ud.begin(), ud.end()); return ld; } // ----- // Fast Factorization // https://judge.yosupo.jp/submission/38126 // !!! CHANGED THE PRIMARY TEST !!! typedef unsigned int uint; struct Mint { uint64_t n; static uint64_t mod, inv, r2; Mint() : n(0) { } Mint(const uint64_t &x) : n(init(x)) { } static uint64_t init(const uint64_t &w) { return reduce(__uint128_t(w) * r2); } static void set_mod(const uint64_t &m) { mod = inv = m; for(int i = 0; i < 5; i++) inv *= 2 - inv * m; r2 = -__uint128_t(m) % m; } static uint64_t reduce(const __uint128_t &x) { uint64_t y = uint64_t(x >> 64) - uint64_t((__uint128_t(uint64_t(x) * inv) * mod) >> 64); return int64_t(y) < 0 ? y + mod : y; } Mint& operator+= (const Mint &x) { n += x.n - mod; if(int64_t(n) < 0) n += mod; return *this; } Mint& operator+ (const Mint &x) const { return Mint(*this) += x; } Mint& operator*= (const Mint &x) { n = reduce(__uint128_t(n) * x.n); return *this; } Mint& operator* (const Mint &x) const { return Mint(*this) *= x; } uint64_t val() const { return reduce(n); } }; uint64_t Mint::mod, Mint::inv, Mint::r2; bool suspect(const uint64_t &a, const uint64_t &s, uint64_t d, const uint64_t &n) { if(Mint::mod != n) Mint::set_mod(n); Mint x(1), xx(a), o(x), m(n - 1); while(d > 0) { if(d & 1) x *= xx; xx *= xx; d >>= 1; } if(x.n == o.n) return true; for(uint r = 0; r < s; r++) { if(x.n == m.n) return true; x *= x; } return false; } bool is_prime(const uint64_t &n) { if(n <= 1 || (n > 2 && (~n & 1))) return false; uint64_t d = n - 1, s = 0; while(~d & 1) s++, d >>= 1; static const uint64_t a_big[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022}; static const uint64_t a_smo[] = {2, 7, 61}; if(n < 4759123141LL) { for(auto &&p : a_smo) { if(p >= n) break; if(!suspect(p, s, d, n)) return false; } } else { for(auto &&p : a_big) { if(p >= n) break; if(!suspect(p, s, d, n)) return false; } } return true; } uint64_t rho_pollard(const uint64_t &n) { if(~n & 1) return 2u; static random_device rng; uniform_int_distribution rr(1, n - 1); if(Mint::mod != n) Mint::set_mod(n); for(;;) { uint64_t c_ = rr(rng), g = 1, r = 1, m = 500; Mint y(rr(rng)), xx(0), c(c_), ys(0), q(1); while(g == 1) { xx.n = y.n; for(uint i = 1; i <= r; i++) y *= y, y += c; uint64_t k = 0; g = 1; while(k < r && g == 1) { for(uint i = 1; i <= (m > (r - k) ? (r - k) : m); i++) { ys.n = y.n; y *= y; y += c; uint64_t xxx = xx.val(), yyy = y.val(); q *= Mint(xxx > yyy ? xxx - yyy : yyy - xxx); } g = __gcd(q.val(), n); k += m; } r *= 2; } if(g == n) g = 1; while(g == 1) { ys *= ys; ys += c; uint64_t xxx = xx.val(), yyy = ys.val(); g = __gcd(xxx > yyy ? xxx - yyy : yyy - xxx, n); } if(g != n && is_prime(g)) return g; } assert(69 == 420); } template vector inter_factor(const T &n) { if(n < 2) return { }; if(is_prime(n)) return {n}; T d = rho_pollard(static_cast(n)); vector l = inter_factor(d), r = inter_factor(n / d); l.insert(l.end(), r.begin(), r.end()); return l; } template vector factor(T n) { vector f1; for(uint i = 2; i < 100; i += (i & 1) + 1) while(n % i == 0) f1.push_back(i), n /= i; vector f2 = inter_factor(n); f1.insert(f1.end(), f2.begin(), f2.end()); sort(f1.begin(), f1.end()); return f1; } // COMPOSITION // https://qoj.ac/submission/356957 // hos_lyricさんの提出を改変 /* q: rev([0, m]) * [0, n], [t^0] q(t, x) = 1 omitted ret: [0, m-1] * [0, n] */ vector comRec(int m, int n, const vector &as, const vector &qss) { if (!n) { auto ret = as; ret.resize(m, 0); return ret; } // reuse DFT(q(t, -x)); (2n+2) instead of (2n+1) int len; for (len = 2; len < (2*m) * (2*n+2); len <<= 1) {} vector qs(len, 0); for (int i = 0; i < m; ++i) for (int j = 0; j <= n; ++j) qs[i * (2*n+2) + j] = qss[i * (n+1) + j]; internal::butterfly(qs); vector work(len >> 1, 0); for (int k = 0; k < len >> 1; ++k) { work[k] = qs[k << 1] * qs[k << 1 | 1]; swap(qs[k << 1], qs[k << 1 | 1]); } internal::butterfly_inv(work); { mint tmp = mint((int)work.size()).inv(); for (int k = 0; k < len >> 1; ++k) work[k] *= tmp; } vector qqss((2*m) * (n/2+1), 0); for (int i = 0; i < 2*m-1; ++i) for (int j = 0; j <= n/2; ++j) qqss[i * (n/2+1) + j] = work[i * (n+1) + j]; for (int i = 0; i < m; ++i) for (int j = 0; j <= n/2; ++j) qqss[(m+i) * (n/2+1) + j] += qss[i * (n+1) + 2*j] + qss[i * (n+1) + 2*j]; const auto res = comRec(2*m, n/2, as, qqss); vector ps(len, 0); for (int i = 0; i < 2*m; ++i) for (int j = 0; j <= n/2; ++j) ps[i * (2*n+2) + (2*n+1) - (2*j+(n&1))] = res[i * (n/2+1) + j]; internal::butterfly(ps); for (int k = 0; k < len; ++k) ps[k] *= qs[k]; internal::butterfly_inv(ps); { mint tmp = mint((int)ps.size()).inv(); for (int k = 0; k < len; ++k) ps[k] *= tmp; } vector ret(m * (n+1)); for (int i = 0; i < m; ++i) for (int j = 0; j <= n; ++j) ret[i * (n+1) + j] = ps[(m+i) * (2*n+2) + (2*n+1) - j]; for (int i = 0; i < m; ++i) for (int j = 0; j <= n/2; ++j) ret[i * (n+1) + (2*j+(n&1))] += res[i * (n/2+1) + j]; return ret; } /* a(b(x)) transpose and rev: p(x) -> [x^(N-1)] p(x) b(x)^i for each i [x^(N-1)] p(x) / (1 - t b(x)) */ vector com(int n, const vector &as, const vector &bs) { assert((int)as.size() <= n); assert((int)bs.size() <= n); vector qss(n, 0); for (int j = 0; j < (int)bs.size(); ++j) qss[j] = -bs[j]; auto cs = comRec(1, n - 1, as, qss); reverse(cs.begin(), cs.end()); return cs; } int main(){ ios_base::sync_with_stdio(false); cin.tie(NULL); int n, m; cin >> n >> m; n++; vector g(30, vector(n+1)); g[0][1] = 1; g[0][2] = 1; rep(i,0,29){ g[i+1] = com(n+1, g[i], g[i]); g[i+1].resize(n+1); } vector f(n+1); f[1] = 1; rep(i,0,30){ if (m>>i&1){ vector nf = com(n+1, f, g[i]); f = nf; f.resize(n+1); } } cout << f[n].val() << '\n'; }