#include <iostream>
#include <limits>
#include <cfenv>
#include <cmath>
#include <algorithm>
#include <array>
#include <bitset>
#include <cstring>
#include <map>
#include <stack>
#include <set>
#include <tuple>
#include <queue>
#include <vector>
#include <cmath>
#include <random>
#include <math.h>
#include <list>
#include <random>
#include <functional>


#define FOR(i, a, b) for(int (i) = (a); (i) < (b); ++(i))
#define REP(i, n) FOR(i, 0, n)
#define rREP(i, n) for(int (i) = (n) - 1; (i) >= 0; --(i))
#define ALL(TheArray) TheArray.begin(), TheArray.end()

using lli = long long int;
using pii = std::pair<int, int>;

template <class T> inline bool chmax(T& a, T b){
    if(a < b){a = b; return true;}
    return false;
}
template <class T> inline bool chmin(T& a, T b){
    if(a > b){a = b; return true;}
    return false;
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


// constexpr version : N が clang++ : 3e5くらいまでok,  g++ : 2e6くらいまでok
template <int N, long long int mod>
struct CombinationMod{
private:
    using lli = long long int;
public:
    std::array<lli, N+1> Power, Inverse, powInverse;

    CombinationMod(int n){
        init(n);
    }

    void init(int n){
        Power[0] = 1; Inverse[0] = 1; powInverse[0] = 1;
        for(int x = 1; x <= n; ++x) Power[x] = (Power[x-1] * x) % mod;
        powInverse[n] = inverse(Power[n]);
        for(int x = N; x > 0; --x) powInverse[x-1] = (powInverse[x] * x) % mod;
        for(int x = 1; x <= n; ++x) Inverse[x] = (Power[x-1] * powInverse[x]) % mod;
    }
    constexpr void swap(lli &a, lli &b) const {
        a ^= b; b ^= a; a ^= b;
    }
    constexpr lli inverse(lli a) const noexcept{
        lli b = mod, u = 1, v = 0;
        while (b != 0) { 
            lli t = a / b; a -= t * b; swap(a, b);
            u -= t * v; swap(u, v);
        }
        u %= mod; if (u < 0) u += mod;
        return u;
    }
    // nCkを求める関数 : n < k の入力に注意せよ
    inline lli combination(int n, int k) const noexcept{
        if(n < k or k < 0) return 0;
        return ((Power[n] * powInverse[k] % mod) * powInverse[n-k]) % mod;
    }
};




/* fn(x, y) := 2加算をx回, 1加算をy回することによって得られる場合の数
   このとき, fn(M, M)が求める答えの近似になる | 条件緩和　-> 加算をそれぞれ独立に考えられる
   追加制約として, 「全てのインデックスについて選ばれた回数が高々M回」を課す
   ---> 再帰で解くのは少し厳しそう; 何かいい言い換えはないか？
   それぞれの操作の特徴は何か？
   １加算 -> 偶奇反転, 2回で2加算と同一視
   ２加算 -> 偶奇普遍
   追加制約を考えれば奇数の数は高々M個 ; なんか良さげな条件 | いや扱いにくいか？
   操作終了後の総和は 3M である
   最大要素は2Mでバウンドされる
   こんな感じの性質を満たしそう(このくらいしか思いつかない : 場所に制約がないので一松模様なども不可)
   逆にこの条件で構成可能か？可能っぽい気がするけど気もしない...
        大きい数から２加算の操作を割り当てると考えると, 残りの総和は高々M
        2M - K の山に関して (K ≤ M) 他の山の最大は K であるから操作は M - K回行われMを超えない
        あまったところに1加算を割り当てれば良い、そしてこれはMを超えない
        奇数をあらかじめ選んでから残りの数(偶数)の配分を決定ればいい -> よし！
 　雑に数えると
    ∑_k nCk * H((3M - k) / 2, n) | for all k s.t. 0 ≤ k ≤ M , (3M - k) % 2 == 0
   最大山が2M以下が考慮されていない -> その数を除去する
   for all i in 1...M, 残り M - i の問題について考えられる | この最大山の選び方は M通り！
*/

constexpr int _M = 2e6 + 5e5;
constexpr int mod = 998244353;
const CombinationMod<_M, mod> Comb(_M);


int main(void){
    int n, m; scanf("%d%d", &n, &m);
    lli P = 0;
    lli tm = 3 * m, even = tm & 1;
    if(even) tm--; tm /= 2; int e = (n > m) ? m : n;
    while(tm >= 0 and even <= e){
        (P += Comb.combination(n, even) * Comb.combination(n + tm - 1, tm)) %= mod;
        tm--; even += 2;
    }
    lli Q = 0; 
    for(int i = 1; i <= m; ++i) (Q += Comb.combination(m - i + n - 2, m - i)) %= mod;
    (Q *= n) %= mod; 
    P -= Q; if(P < 0) P += mod;
    printf("%lld\n", P % mod);
    return 0;
}

Measured memory_bytes: 58880