// template {{{ #include #if __has_include() # include #else # define dbg(...) static_cast(0) #endif using i32 = std::int32_t; using u32 = std::uint32_t; using i64 = std::int64_t; using u64 = std::uint64_t; using usize = std::size_t; using bit_t = u64; constexpr auto operator""_i32(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator""_i64(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator""_u32(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator""_u64(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator""_uz(unsigned long long n) noexcept { return static_cast(n); } constexpr auto operator""_bit(unsigned long long n) noexcept { return static_cast(n); } template inline constexpr auto INF = std::numeric_limits::max() / N; template constexpr inline std::enable_if_t, T> power(T a, u32 b) { T ans = 1; for (; b; b >>= 1) { if (b & 1) ans *= a; a *= a; } return ans; } template constexpr inline auto TEN(u32 n) { return power(static_cast(10), n); } constexpr inline auto countr_zero(bit_t n) -> int { return n == 0 ? 64 : __builtin_ctzll(n); } constexpr inline auto countl_zero(bit_t n) -> int { return n == 0 ? 64 : __builtin_clzll(n); } constexpr inline auto countr_one(bit_t n) -> int { return n == std::numeric_limits::max() ? 64 : countr_zero(compl n); } constexpr inline auto countl_one(bit_t n) -> int { return n == std::numeric_limits::max() ? 64 : countl_zero(compl n); } constexpr inline auto mask(u32 n) -> bit_t { return (static_cast(1) << n) - 1; } constexpr inline auto popcount(bit_t n) -> int { return __builtin_popcountll(n); } constexpr inline auto is_pow2(bit_t n) -> bool { return (n & (n - 1)) == 0; } constexpr inline auto msb(bit_t n) -> int { return 63 - countl_zero(n); } constexpr inline auto bit_ceil(bit_t n) -> bit_t { return is_pow2(n) ? n : static_cast(1) << msb(n); } constexpr inline auto bit_floor(bit_t n) -> bit_t { return is_pow2(n) ? n : static_cast(1) << (msb(n) + 1); } template constexpr auto chmin(T& a, U&& b) -> bool { return b < a ? a = std::forward(b), true : false; } template constexpr auto chmax(T& a, U&& b) -> bool { return a < b ? a = std::forward(b), true : false; } template struct is_tuple_like: std::false_type {}; template struct is_tuple_like>: std::true_type {}; template struct is_tuple_like>: std::true_type {}; template struct is_tuple_like>: std::true_type {}; template> struct is_printable: std::false_type {}; template struct is_printable())>>: std::true_type {}; template> struct is_iteratable: std::false_type {}; template struct is_iteratable()), std::end(std::declval()))>>: std::true_type {}; template> struct has_val: std::false_type {}; template struct has_val().val())>>: std::true_type {}; template struct is_1indexed: std::false_type {}; #define INDEXED_IMPL(type) \ struct type##_##1 { \ using base = type; \ }; \ template<> struct is_1indexed: std::true_type {}; INDEXED_IMPL(int) INDEXED_IMPL(size_t) INDEXED_IMPL(i32) INDEXED_IMPL(u32) INDEXED_IMPL(i64) INDEXED_IMPL(u64) INDEXED_IMPL(usize) #undef INDEXED_IMPL template struct rec_lambda { F f; explicit constexpr rec_lambda(F&& f): f(std::forward(f)) {} template constexpr decltype(auto) operator()(Args&&... args) const { return f(*this, std::forward(args)...); } }; template auto zip(const std::vector&... args) { std::size_t n = std::min({ std::size(args)... }); std::vector...>> ret(n); for (std::size_t i = 0; i < n; ++i) ret[i] = { args[i]... }; return ret; } template auto construct_vector(std::vector& sizes, T x = std::decay_t{}) { if constexpr (N == 1) { return std::vector>(sizes[0], x); } else { size_t size = sizes[N - 1]; sizes.pop_back(); return std::vector(size, construct_vector(sizes, x)); } } template decltype(auto) make_vector(size_t (&&sizes)[N], T&& x = std::decay_t{}) { std::vector s(N); for (size_t i = 0; i < N; ++i) s[i] = sizes[N - i - 1]; if constexpr (std::is_invocable_v>) { auto ret = construct_vector>, N>(s); rec_lambda([](auto&& self, auto& v, auto&& f) { for (auto it = std::begin(v); it != std::end(v); ++it) { if constexpr (std::is_same_v, std::invoke_result_t>) *it = f(); else self(*it, f); } })(ret, x); return ret; } else { return construct_vector, N>(s, x); } } template auto make_graph(size_t n, const std::vector>& edges, bool is_directed = false) { std::vector graph(n, std::vector{}); for (const auto& [u, v]: edges) { graph[u].emplace_back(v); if (not is_directed) graph[v].emplace_back(u); } return graph; } template auto make_graph(size_t n, const std::vector>& edges, bool is_directed = false) { std::vector graph(n, std::vector>{}); for (const auto& [u, v, w]: edges) { graph[u].emplace_back(v, w); if (not is_directed) graph[v].emplace_back(u, w); } return graph; } __attribute__((constructor)) auto io_setup() noexcept { std::ios::sync_with_stdio(false); std::cin.tie(nullptr); std::cout << std::fixed << std::setprecision(10); std::cerr << std::fixed << std::setprecision(10); } // Copyright (c) 2021 Mitama Lab | `tuple_scan`, `scan`, `in` are based on the code released under the ISC license. see // https://opensource.org/licenses/ISC. template inline decltype(auto) tuple_scan(Tp&, std::index_sequence); inline auto scan = [](auto&... args) { return (..., [&] { if constexpr (is_tuple_like>::value) { return tuple_scan(args, std::make_index_sequence>>{}); } else { return not (std::cin >> args).fail(); } }()); }; template inline auto tuple_scan(Tp& tup, std::index_sequence) { return (... and scan(std::get(tup))); } template decltype(auto) inline in(); template inline auto tuple_in(std::index_sequence) { return std::tuple{ in>()... }; } template decltype(auto) inline in() { if constexpr (sizeof...(Args) == 0) { if constexpr (is_tuple_like::value) { auto t = tuple_in(std::make_index_sequence>()); return t; } else if constexpr (is_1indexed::value) { typename T::base x; scan(x); --x; return x; } else { T x; scan(x); return x; } } else { return std::tuple{ in(), in()... }; } } template inline auto in_vec(size_t&&... args) { return make_vector({ static_cast(args)... }, *in); } inline constexpr char sep = ' '; inline constexpr char eoln = '\n'; inline constexpr auto yes = "Yes"; inline constexpr auto no = "No"; inline auto print(){}; template inline auto print(T&&) -> void; template inline auto tuple_print(Tp&& tp, std::index_sequence) -> void { ( [&] { print(std::forward(tp))>(std::get(tp))); if constexpr (I + 1 != std::tuple_size_v>) print(sep); }(), ...); } template constexpr bool false_v = false; template inline auto print(T&& x) -> void { if constexpr (is_printable>::value) { if constexpr (std::is_same_v>) std::cout << (x ? yes : no); else std::cout << x; } else if constexpr (is_tuple_like>::value) { tuple_print(std::forward(x), std::make_index_sequence>>()); } else if constexpr (is_iteratable::value) { for (auto it = std::begin(x); it != std::end(x); ++it) { print(std::forward(*it)); if (next(it) != std::end(x)) print(sep); } } else if (has_val>::value) { print(x.val()); } } template inline auto print(T&& t, Args&&... args) { print(std::forward(t)); print(sep); print(std::forward(args)...); } template inline auto println(Args&&... args) { print(std::forward(args)...); print(eoln); } template [[noreturn]] inline auto drop(Args&&... args) { println(std::forward(args)...); exit(0); } #define overload2(_NULL, _1, _2, name, ...) name #define rep1(i, n) for (std::decay_t i = 0; i < (n); i++) #define rep2(i, a, b) for (std::decay_t i = (a); i < (b); i++) #define rep(...) overload2(__VA_ARGS__, rep2, rep1)(__VA_ARGS__) #define LAMBDA(...) [&]([[maybe_unused]] const auto& _) { return __VA_ARGS__; } #define LAMBDA2(...) [&](const auto& _1, const auto& _2) { return __VA_ARGS__; } // }}} /** * @brief Modint */ /** * @brief Extended Euclid's Algorithm * @note return pair of minimum x, y s.t. ax + by = gcd(x, y) * @ref https://noshi91.hatenablog.com/entry/2019/04/01/184957 */ constexpr std::pair extgcd(intmax_t a, intmax_t b) { intmax_t s = a, xs = 1, ys = 0, t = b, xt = 0, yt = 1; while (s % t != 0) { const intmax_t tmp = s / t, u = s - t * tmp, xu = xs - xt * tmp, yu = ys - yt * tmp; s = t, xs = xt, ys = yt; t = u, xt = xu, yt = yu; } assert(t == std::gcd(a, b)); return { xt, yt }; } template struct Modint { using value_type = decltype(Modulo); private: value_type value = 0; template static constexpr value_type normalize(T n) { if (static_cast>(n) >= Modulo) n %= Modulo; if (n < 0) (n %= Modulo) += Modulo; return n; } public: constexpr Modint() noexcept: value(0) {} template, std::nullptr_t> = nullptr> constexpr Modint(const T& n): value(normalize(n)) {} template constexpr std::enable_if_t, T> val() const noexcept { return static_cast(value); } template static constexpr std::enable_if_t, T> mod() noexcept { return static_cast(Modulo); } template static constexpr auto raw(const T& v) noexcept { Modint tmp{}; tmp.value = static_cast(v); return tmp; } constexpr auto operator+() const noexcept { return *this; } constexpr auto operator-() const noexcept { if (value == 0) return *this; return Modint::raw(mod() - value); } constexpr bool operator==(const Modint& rhs) { return value == rhs.value; } constexpr bool operator!=(const Modint& rhs) { return value != rhs.value; } constexpr auto& operator++() noexcept { if (++value == mod()) value = 0; return *this; } constexpr auto& operator--() noexcept { if (value-- == 0) value = mod() - 1; return *this; } constexpr auto operator++(int) { const auto tmp{ *this }; ++*this; return tmp; } constexpr auto operator--(int) { const auto tmp{ *this }; --*this; return tmp; } constexpr auto operator+(const Modint& rhs) const noexcept { return Modint{ *this } += rhs; } constexpr auto operator-(const Modint& rhs) const noexcept { return Modint{ *this } -= rhs; } constexpr auto operator*(const Modint& rhs) const noexcept { return Modint{ *this } *= rhs; } constexpr auto operator/(const Modint& rhs) const noexcept { return Modint{ *this } /= rhs; } constexpr auto& operator+=(const Modint& rhs) noexcept { if ((value += rhs.value) >= mod()) value -= mod(); return *this; } constexpr auto& operator-=(const Modint& rhs) noexcept { if ((value -= rhs.value) < 0) value += mod(); return *this; } constexpr auto& operator*=(const Modint& rhs) noexcept { value = static_cast(static_cast(value) * static_cast(rhs.value) % mod()); return *this; } constexpr auto& operator/=(const Modint& rhs) noexcept { return *this *= rhs.inv(); } constexpr auto inv() const { return Modint{ extgcd(this->val(), this->mod()).first }; } template constexpr auto pow(T n) const { auto a = *this, ans = raw(1); while (n != 0) { if (n & 1) ans *= a; a *= a; n >>= 1; } return ans; } constexpr friend std::ostream& operator<<(std::ostream& os, const Modint& arg) { os << arg.value; return os; } constexpr friend std::istream& operator>>(std::istream& os, Modint& arg) { os >> arg.value; return os; } }; using Modint1000000007 = Modint<1000000007>; using Modint998244353 = Modint<998244353>; template class ModTable { std::vector m_fact, m_fact_inv, m_inv; public: constexpr ModTable() = default; constexpr ModTable(const size_t n): m_fact(n + 1), m_fact_inv(n + 1), m_inv(n + 1) { m_inv[1] = 1; for (size_t i = 2; i <= n; ++i) m_inv[i] = -m_inv[M::mod() % i] * (M::mod() / i); m_fact[0] = 1; for (size_t i = 0; i < n; i++) m_fact[i + 1] = m_fact[i] * M{ i + 1 }; m_fact_inv[n] = m_fact[n].inv(); for (size_t i = n; i--;) m_fact_inv[i] = m_fact_inv[i + 1] * M{ i + 1 }; } constexpr auto inv(const int i) const { assert(i != 0); return m_inv[i]; } constexpr auto fact(const int i) const { return m_fact[i]; } constexpr auto fact_inv(const int i) const { return m_fact_inv[i]; } constexpr auto perm(const int i, const int j) const { return i >= j ? fact(i) * fact_inv(i - j) : 0; } constexpr auto binom(const int i, const int j) const { if (i < 0 or j < 0 or i < j) return M::raw(0); return m_fact[i] * m_fact_inv[j] * m_fact_inv[i - j]; } constexpr auto homo(const int i, const int j) const { if (i == 0 and j == 0) return M::raw(1); return binom(i + j - 1, j); } }; using namespace std; int main() { auto [a, b] = in(); --a; --b; println(ModTable(200000).binom(a + b, a)); }