macro_rules !read_value {($iter :expr ,($($t :tt ) ,*) ) =>{($(read_value !($iter ,$t ) ) ,*) } ;($iter :expr ,[$t :tt ;$len :expr ] ) =>{(0 ..$len ) .map (|_ |read_value !($iter ,$t ) ) .collect ::<Vec <_ >>() } ;($iter :expr ,{chars :$base :expr } ) =>{read_value !($iter ,String ) .chars () .map (|c |(c as u8 -$base as u8 ) as usize ) .collect ::<Vec <usize >>() } ;($iter :expr ,{char :$base :expr } ) =>{read_value !($iter ,{chars :$base } ) [0 ] } ;($iter :expr ,chars ) =>{read_value !($iter ,String ) .chars () .collect ::<Vec <char >>() } ;($iter :expr ,char ) =>{read_value !($iter ,chars ) [0 ] } ;($iter :expr ,usize1 ) =>{read_value !($iter ,usize ) -1 } ;($iter :expr ,$t :ty ) =>{$iter .next () .unwrap () .parse ::<$t >() .unwrap () } ;}
macro_rules !input_inner {($iter :expr ) =>{} ;($iter :expr ,) =>{} ;($iter :expr ,mut $var :ident :$t :tt $($r :tt ) *) =>{let mut $var =read_value !($iter ,$t ) ;input_inner !{$iter $($r ) *} } ;($iter :expr ,mut $var :ident $($r :tt ) *) =>{input_inner !{$iter ,mut $var :usize $($r ) *} } ;($iter :expr ,$var :ident :$t :tt $($r :tt ) *) =>{let $var =read_value !($iter ,$t ) ;input_inner !{$iter $($r ) *} } ;($iter :expr ,$var :ident $($r :tt ) *) =>{input_inner !{$iter ,$var :usize $($r ) *} } ;}
macro_rules !input {(source =$s :expr ,$($r :tt ) *) =>{let mut iter =$s .split_whitespace () ;input_inner !{iter ,$($r ) *} } ;(iter =$iter :ident ,$($r :tt ) *) =>{let s ={use std ::io ::Read ;let mut s =String ::new () ;std ::io ::stdin () .read_to_string (&mut s ) .unwrap () ;s } ;let mut $iter =s .split_whitespace () ;input_inner !{$iter ,$($r ) *} } ;($($r :tt ) *) =>{let s ={use std ::io ::Read ;let mut s =String ::new () ;std ::io ::stdin () .read_to_string (&mut s ) .unwrap () ;s } ;let mut iter =s .split_whitespace () ;input_inner !{iter ,$($r ) *} } ;}
const MOD: i64 = 998244353;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Modi64(pub i64);
impl Modi64 {
    #[inline]
    pub fn new(i: i64) -> Self {
        Modi64(Self::modulo(i, MOD))
    }
    #[inline]
    pub fn modulo(i: i64, m: i64) -> i64 {
        let mut x = i % m;
        if x < 0 {
            x += m;
        }
        x
    }
    pub fn pow(self, y: i64) -> Self {
        let mut y = Self::modulo(y, MOD - 1);
        let mut x = Self::new(1);
        let mut base = self;
        while y > 0 {
            if y & 1 == 1 {
                x *= base;
            }
            base *= base;
            y = y >> 1;
        }
        x
    }
    #[inline]
    pub fn inv(self) -> Self {
        let (mut x, mut s, mut t, mut u) = (1, self.0, MOD, 0);
        while t != 0 {
            let k = s / t;
            s -= k * t;
            std::mem::swap(&mut s, &mut t);
            x -= k * u;
            std::mem::swap(&mut x, &mut u);
        }
        Modi64::new(x)
    }
}
impl From<i64> for Modi64 {
    fn from(i: i64) -> Self {
        Self::new(i)
    }
}
impl std::ops::Add for Modi64 {
    type Output = Self;
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        Modi64((self.0 + rhs.0) % MOD)
    }
}
impl<'a> std::ops::Add<Modi64> for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn add(self, rhs: Modi64) -> Self::Output {
        *self + rhs
    }
}
impl<'a> std::ops::Add<&'a Modi64> for Modi64 {
    type Output = Modi64;
    #[inline]
    fn add(self, rhs: &'a Modi64) -> Self::Output {
        self + *rhs
    }
}
impl<'a> std::ops::Add for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn add(self, rhs: Self) -> Self::Output {
        *self + *rhs
    }
}
impl std::ops::AddAssign for Modi64 {
    #[inline]
    fn add_assign(&mut self, rhs: Self) {
        *self = *self + rhs;
    }
}
impl<'a> std::ops::AddAssign<&'a Modi64> for Modi64 {
    #[inline]
    fn add_assign(&mut self, rhs: &'a Modi64) {
        *self += *rhs;
    }
}
impl std::ops::Sub for Modi64 {
    type Output = Self;
    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        Self::new(self.0 - rhs.0)
    }
}
impl<'a> std::ops::Sub<Modi64> for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn sub(self, rhs: Modi64) -> Self::Output {
        *self - rhs
    }
}
impl<'a> std::ops::Sub<&'a Modi64> for Modi64 {
    type Output = Modi64;
    #[inline]
    fn sub(self, rhs: &'a Modi64) -> Self::Output {
        self - *rhs
    }
}
impl<'a> std::ops::Sub for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn sub(self, rhs: Self) -> Self::Output {
        *self - *rhs
    }
}
impl std::ops::SubAssign for Modi64 {
    #[inline]
    fn sub_assign(&mut self, rhs: Self) {
        *self = *self - rhs;
    }
}
impl<'a> std::ops::SubAssign<&'a Modi64> for Modi64 {
    #[inline]
    fn sub_assign(&mut self, rhs: &'a Modi64) {
        *self -= *rhs;
    }
}
impl std::ops::Mul for Modi64 {
    type Output = Self;
    #[inline]
    fn mul(self, rhs: Self) -> Self::Output {
        Modi64(self.0 * rhs.0 % MOD)
    }
}
impl<'a> std::ops::Mul<Modi64> for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn mul(self, rhs: Modi64) -> Self::Output {
        *self * rhs
    }
}
impl<'a> std::ops::Mul<&'a Modi64> for Modi64 {
    type Output = Modi64;
    #[inline]
    fn mul(self, rhs: &'a Modi64) -> Self::Output {
        self * *rhs
    }
}
impl<'a> std::ops::Mul for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn mul(self, rhs: Self) -> Self::Output {
        *self * *rhs
    }
}
impl std::ops::MulAssign for Modi64 {
    #[inline]
    fn mul_assign(&mut self, rhs: Self) {
        *self = *self * rhs;
    }
}
impl<'a> std::ops::MulAssign<&'a Modi64> for Modi64 {
    #[inline]
    fn mul_assign(&mut self, rhs: &'a Modi64) {
        *self *= *rhs;
    }
}
impl std::ops::Div for Modi64 {
    type Output = Self;
    #[inline]
    fn div(self, rhs: Self) -> Self::Output {
        self * rhs.inv()
    }
}
impl<'a> std::ops::Div<Modi64> for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn div(self, rhs: Modi64) -> Self::Output {
        *self * rhs.inv()
    }
}
impl<'a> std::ops::Div<&'a Modi64> for Modi64 {
    type Output = Modi64;
    #[inline]
    fn div(self, rhs: &'a Modi64) -> Self::Output {
        self * rhs.inv()
    }
}
impl<'a> std::ops::Div for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn div(self, rhs: Self) -> Self::Output {
        *self * rhs.inv()
    }
}
impl std::ops::DivAssign for Modi64 {
    #[inline]
    fn div_assign(&mut self, rhs: Self) {
        *self = *self * rhs.inv();
    }
}
impl<'a> std::ops::DivAssign<&'a Modi64> for Modi64 {
    #[inline]
    fn div_assign(&mut self, rhs: &'a Modi64) {
        *self *= rhs.inv();
    }
}
impl std::ops::Neg for Modi64 {
    type Output = Self;
    #[inline]
    fn neg(self) -> Self::Output {
        Self::new(-self.0)
    }
}
impl<'a> std::ops::Neg for &'a Modi64 {
    type Output = Modi64;
    #[inline]
    fn neg(self) -> Self::Output {
        -*self
    }
}
impl std::iter::Sum for Modi64 {
    #[inline]
    fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
        iter.fold(Self::new(0), std::ops::Add::add)
    }
}
impl<'a> std::iter::Sum<&'a Modi64> for Modi64 {
    #[inline]
    fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
        iter.fold(Self::new(0), |x, &y| x + y)
    }
}
impl std::fmt::Display for Modi64 {
    fn fmt<'a>(&self, f: &mut std::fmt::Formatter<'a>) -> Result<(), std::fmt::Error> {
        write!(f, "{}", self.0)
    }
}
impl std::str::FromStr for Modi64 {
    type Err = std::num::ParseIntError;
    #[inline]
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        s.parse::<i64>().map(|i| Modi64::new(i))
    }
}
#[derive(Clone, Debug)]
pub struct MemorizedFactorial {
    n: usize,
    fact: Vec<Modi64>,
    inv_fact: Vec<Modi64>,
}
impl MemorizedFactorial {
    pub fn new(max_n: usize) -> MemorizedFactorial {
        let mut fact = vec![Modi64::new(1); max_n + 1];
        let mut inv_fact = vec![Modi64::new(1); max_n + 1];
        for i in 1..(max_n + 1) {
            fact[i] = fact[i - 1] * Modi64::new(i as i64);
            inv_fact[i] = inv_fact[i - 1] / Modi64::new(i as i64);
        }
        MemorizedFactorial {
            n: max_n + 1,
            fact: fact,
            inv_fact: inv_fact,
        }
    }
    pub fn combination(&self, n: usize, r: usize) -> Modi64 {
        if r <= n {
            self.fact[n] * self.inv_fact[r] * self.inv_fact[n - r]
        } else {
            Modi64::new(0)
        }
    }
    pub fn permutation(&self, n: usize, r: usize) -> Modi64 {
        if r <= n {
            self.fact[n] * self.inv_fact[n - r]
        } else {
            Modi64::new(0)
        }
    }
    pub fn homogeneous_product(&self, n: usize, r: usize) -> Modi64 {
        if n != 0 && r != 0 {
            self.combination(n + r - 1, r)
        } else {
            Modi64::new(1)
        }
    }
}
fn main() {
    input! { n, m };
    let mut ans = Modi64(0);
    let fact = MemorizedFactorial::new(n + m * 3 / 2 + 2);
    for i in 0..m + 1 {
        if (m + i) % 2 == 0 {
            ans += fact.combination(n, i) * fact.combination((3 * m - i) / 2 + n - 1, n - 1);
        }
    }
    for i in 0..m + 1 {
        if (m + i) % 2 == 0 {
            ans -= fact.combination(n, i)
                * fact.combination((m - i) / 2 + n - 1, n - 1)
                * Modi64(n as i64);
        }
    }
    for i in 0..m + 1 {
        if (m + i) % 2 == 0 {
            ans += fact.combination(n - 1, i)
                * fact.combination((m - i) / 2 + n - 2, n - 2)
                * Modi64(n as i64);
        }
    }
    println!("{}", ans);
}


Measured memory_bytes: 33020