use std::io::*;
use std::str::FromStr;

fn fail() {
    println!("{}", 0);
    std::process::exit(0);
}

fn main() {
    let cin = stdin();
    let cin = cin.lock();
    let mut sc = Scanner::new(cin);

    let mut x: u32 = sc.next();
    let mut y: u32 = sc.next();

    if (x + y) % 3 != 0 {
        fail();
    }
    let n = (x + y) / 3;
    if x < n || y < n {
        fail();
    }
    x -= n;
    y -= n;

    println!("{}", combi::<ModInt>(x + y, x));
}

/* ========== Combination ========== */

fn fact<T>(n: u32) -> T
where
    T: std::ops::MulAssign + std::convert::From<u32>,
{
    let mut x = T::from(1);
    for i in 1..(n + 1) {
        x *= T::from(i);
    }
    x
}

fn combi<T>(n: u32, m: u32) -> T
where
    T: std::ops::MulAssign + std::ops::Div<Output = T> + std::convert::From<u32>,
{
    if n < m {
        T::from(0)
    } else {
        fact::<T>(n) / fact::<T>(m) / fact::<T>(n - m)
    }
}

/* ========== MotInt ========== */

const MOD: u32 = 1_000_000_007;

#[derive(Clone, Copy)]
struct ModInt(u32);

// Fundamental
#[allow(dead_code)]
impl ModInt {
    pub fn new(x: u32) -> ModInt {
        ModInt(x % MOD)
    }

    pub fn pow(mut self, mut n: u32) -> ModInt {
        let mut ret = ModInt::new(1);
        while n > 0 {
            if n & 1 == 1 {
                ret *= self;
            }
            self *= self;
            n >>= 1;
        }
        ret
    }
}

impl std::convert::From<u32> for ModInt {
    fn from(x: u32) -> ModInt {
        ModInt::new(x)
    }
}

// Assign
impl std::ops::AddAssign for ModInt {
    fn add_assign(&mut self, rhs: ModInt) {
        self.0 += rhs.0;
        if self.0 >= MOD {
            self.0 -= MOD;
        };
    }
}

impl std::ops::SubAssign for ModInt {
    fn sub_assign(&mut self, rhs: ModInt) {
        self.0 += MOD - rhs.0;
        if self.0 >= MOD {
            self.0 -= MOD;
        };
    }
}

impl std::ops::MulAssign for ModInt {
    fn mul_assign(&mut self, rhs: ModInt) {
        self.0 = ((self.0 as u64) * (rhs.0 as u64) % (MOD as u64)) as u32;
    }
}

impl std::ops::DivAssign for ModInt {
    fn div_assign(&mut self, rhs: ModInt) {
        *self *= rhs.pow(MOD - 2);
    }
}

// Binary operator
impl std::ops::Add for ModInt {
    type Output = ModInt;
    fn add(mut self, rhs: ModInt) -> Self::Output {
        self += rhs;
        self
    }
}

impl std::ops::Sub for ModInt {
    type Output = ModInt;
    fn sub(mut self, rhs: ModInt) -> Self::Output {
        self -= rhs;
        self
    }
}

impl std::ops::Mul for ModInt {
    type Output = ModInt;
    fn mul(mut self, rhs: ModInt) -> Self::Output {
        self *= rhs;
        self
    }
}

impl std::ops::Div for ModInt {
    type Output = ModInt;
    fn div(mut self, rhs: ModInt) -> Self::Output {
        self /= rhs;
        self
    }
}

// Output
impl std::fmt::Display for ModInt {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

/* ========== Scanner ========== */

struct Scanner<R: Read> {
    reader: R,
}

#[allow(dead_code)]
impl<R: Read> Scanner<R> {
    fn new(reader: R) -> Scanner<R> {
        Scanner { reader: reader }
    }

    fn read<T: FromStr>(&mut self) -> Option<T> {
        let token = self
            .reader
            .by_ref()
            .bytes()
            .map(|c| c.unwrap() as char)
            .skip_while(|c| c.is_whitespace())
            .take_while(|c| !c.is_whitespace())
            .collect::<String>();
        if token.is_empty() {
            None
        } else {
            token.parse::<T>().ok()
        }
    }

    fn next<T: FromStr>(&mut self) -> T {
        if let Some(s) = self.read() {
            s
        } else {
            writeln!(stderr(), "Terminated with EOF").unwrap();
            std::process::exit(0);
        }
    }

    fn vec<T: FromStr>(&mut self, n: usize) -> Vec<T> {
        (0..n).map(|_| self.next()).collect()
    }

    fn char(&mut self) -> char {
        self.next::<String>().chars().next().unwrap()
    }
}


Measured memory_bytes: 4352