Problem:
Score : 300 points

Problem Statement

There is a grid with infinitely many rows and columns. In this grid, there is a rectangular region with consecutive N rows and M columns, and a card is placed in each square in this region.
The front and back sides of these cards can be distinguished, and initially every card faces up.

We will perform the following operation once for each square contains a card:

For each of the following nine squares, flip the card in it if it exists: the target square itself and the eight squares that shares a corner or a side with the target square.

It can be proved that, whether each card faces up or down after all the operations does not depend on the order the operations are performed.
Find the number of cards that face down after all the operations.

Constraints

1 \leq N,M \leq 10^9

All input values are integers.

Input

Input is given from Standard Input in the following format:

N M

Output

Print the number of cards that face down after all the operations.

Sample Input 1

2 2

Sample Output 1

0

We will flip every card in any of the four operations. Thus, after all the operations, all cards face up.

Sample Input 2

1 7

Sample Output 2

5

After all the operations, all cards except at both ends face down.

Sample Input 3

314 1592

Sample Output 3

496080

Solution in Rust (1.15.1) (judge verdict: Accepted)

pub use util::prelude::*;

pub mod util {
    //! https://github.com/dmizuk/competito-rs
    //!
    //! Copyright (c) 2018 Daiki Mizukami.
    //!
    //! Licensed under the Unlicense unless explicitly mentioned.


    pub mod prelude {
        pub use super::common::prelude::*;
        pub use super::io::prelude::*;
        pub use super::num::prelude::*;
    }

    pub mod common {
        use std::fmt::Debug;
        use std::mem;


        pub mod prelude {
            pub use super::IteratorUnwrappedExt;
            pub use super::IteratorTakeHeadsExt;
            pub use super::memchr;
            pub use super::memchr2;
            pub use super::memchr3;
        }

        pub trait Unwrap {
            type Item;

            fn unwrap(self) -> Self::Item;
        }

        impl<T, E: Debug> Unwrap for Result<T, E> {
            type Item = T;

            fn unwrap(self) -> T {
                Result::unwrap(self)
            }
        }

        impl<T> Unwrap for Option<T> {
            type Item = T;

            fn unwrap(self) -> T {
                Option::unwrap(self)
            }
        }


        pub trait IteratorUnwrappedExt: Sized {
            fn unwrapped(self) -> Unwrapped<Self>;
        }

        impl<I> IteratorUnwrappedExt for I
            where I: Iterator, I::Item: Unwrap
        {
            fn unwrapped(self) -> Unwrapped<Self> {
                Unwrapped(self)
            }
        }

        pub struct Unwrapped<I>(I);

        impl<I> Iterator for Unwrapped<I>
            where I: Iterator, I::Item: Unwrap
        {
            type Item = <I::Item as Unwrap>::Item;

            fn next(&mut self) -> Option<Self::Item> {
                self.0.next().map(Unwrap::unwrap)
            }
        }

        pub trait IteratorTakeHeadsExt: Sized {
            fn take_heads(self) -> TakeHeads<Self>;
        }

        impl<I: Iterator> IteratorTakeHeadsExt for I {
            fn take_heads(self) -> TakeHeads<Self> {
                TakeHeads(self)
            }
        }

        pub struct TakeHeads<I>(I);

        impl<I: Iterator> Iterator for TakeHeads<I> {
            type Item = I::Item;

            fn next(&mut self) -> Option<I::Item> {
                let ret = self.0.next();
                if ret.is_some() {
                    while let Some(_) = self.0.next() {}
                }
                ret
            }
        }


        pub trait FromByteStream: Sized {
            type Parser: ParseByteStream<Output=Self>;

            fn parser() -> Self::Parser;
        }

        pub trait ParseByteStream {
            type Output;

            fn eat(&mut self, slice: &[u8]);
            fn take(&mut self) -> Self::Output;
        }

        impl FromByteStream for () {
            type Parser = ();

            fn parser() -> () {}
        }

        impl ParseByteStream for () {
            type Output = ();

            fn eat(&mut self, _: &[u8]) {}
            fn take(&mut self) -> () {}
        }

        impl FromByteStream for String {
            type Parser = StringParser;

            fn parser() -> StringParser {
                Default::default()
            }
        }

        #[derive(Default)]
        pub struct StringParser(Vec<u8>);

        impl ParseByteStream for StringParser {
            type Output = String;

            fn eat(&mut self, s: &[u8]) {
                self.0.extend_from_slice(s)
            }

            fn take(&mut self) -> String {
                let vec = mem::replace(&mut self.0, Vec::new());
                String::from_utf8(vec).unwrap()
            }
        }

        #[derive(Default)]
        pub struct UnsignedParser<T>(T);

        macro_rules! parse_unsigned {
            ($($t:ty),*) => {$(
                impl FromByteStream for $t {
                    type Parser = UnsignedParser<$t>;

                    fn parser() -> UnsignedParser<$t> {
                        Default::default()
                    }
                }

                impl ParseByteStream for UnsignedParser<$t> {
                    type Output = $t;

                    fn eat(&mut self, s: &[u8]) {
                        for &b in s {
                            let n: $t = atoi(b)
                                .expect("UnsignedParser::eat: invalid digit")
                                .into();
                            self.0 = self.0
                                .checked_mul(10).unwrap()
                                .checked_add(n).unwrap();
                        }
                    }

                    fn take(&mut self) -> $t {
                        mem::replace(&mut self.0, 0)
                    }
                }
            )*};
        }

        pub struct SignedParser<T>(SignedParserState<T>);

        impl<T> Default for SignedParser<T> {
            fn default() -> Self {
                SignedParser(SignedParserState::None)
            }
        }

        macro_rules! parse_signed {
            ($($t:ty),*) => {$(
                impl FromByteStream for $t {
                    type Parser = SignedParser<$t>;

                    fn parser() -> SignedParser<$t> {
                        Default::default()
                    }
                }

                impl ParseByteStream for SignedParser<$t> {
                    type Output = $t;

                    fn eat(&mut self, s: &[u8]) {
                        use self::SignedParserState::*;

                        const INVALID_DIGIT: &'static str =
                            "SignedParser::eat: invalid digit";

                        match self.0 {
                            Signed(ref mut n) | Unsigned(ref mut n) => {
                                for &b in s {
                                    let d = atoi(b).expect(INVALID_DIGIT) as $t;
                                    *n = n.checked_mul(10).unwrap()
                                        .checked_add(d).unwrap();
                                }
                            },
                            SignedNone => if let Some((&b, rest))
                                = s.split_first()
                            {
                                self.0 = if let Some(n) = atoi(b) {
                                    Signed(n as $t)
                                } else {
                                    panic!(INVALID_DIGIT);
                                };

                                self.eat(rest);
                            },
                            None => if let Some((&b, rest)) = s.split_first() {
                                self.0 = if let Some(n) = atoi(b) {
                                    Unsigned(n as $t)
                                } else if b'-' == b {
                                    SignedNone
                                } else {
                                    panic!(INVALID_DIGIT);
                                };

                                self.eat(rest);
                            },
                        }
                    }

                    fn take(&mut self) -> $t {
                        use self::SignedParserState::*;

                        match mem::replace(&mut self.0, None) {
                            Unsigned(n) => n,
                            Signed(n) => n.checked_neg().unwrap(),
                            SignedNone | None => panic!(
                                "SignedParser::take: unexpected end of input"
                            ),
                        }
                    }
                }
            )*};
        }

        enum SignedParserState<T> {
            Unsigned(T),
            Signed(T),
            SignedNone,
            None,
        }

        fn atoi(c: u8) -> Option<u8> {
            if b'0' <= c && c <= b'9' {
                Some(c - b'0')
            } else {
                None
            }
        }

        parse_unsigned! { u8, u16, u32, u64, usize }
        parse_signed! { i8, i16, i32, i64, isize }


        pub fn memchr(c: u8, src: &[u8]) -> Option<usize> {
            use std::os::raw::{c_int, c_void};
            use super::ffi::memchr;

            let p = unsafe {
                memchr(src.as_ptr() as *const c_void, c as c_int, src.len())
            };

            if p.is_null() {
                None
            } else {
                Some(p as usize - src.as_ptr() as usize)
            }
        }


        pub use self::rust_memchr::*;

        // Code derived from Andrew Gallant's rust-memchr:
        // https://github.com/BurntSushi/rust-memchr/blob/df1092f/src/lib.rs
        // Licensed under the Unlicense.
        mod rust_memchr {
            use std::cmp;
            use std::mem;

            fn repeat_byte(b: u8) -> usize {
                let mut rep = (b as usize) << 8 | b as usize;
                rep = rep << 16 | rep;
                rep = rep << 32 | rep;
                rep
            }

            fn contains_zero_byte(x: usize) -> bool {
                x.wrapping_sub(0x0101010101010101) & !x & 0x8080808080808080 != 0
            }

            pub fn memchr2(needle1: u8, needle2: u8, haystack: &[u8])
                -> Option<usize>
            {
                fn slow(b1: u8, b2: u8, haystack: &[u8]) -> Option<usize> {
                    haystack.iter().position(|&b| b == b1 || b == b2)
                }

                #[allow(non_snake_case)]
                let USIZE_BYTES: usize = mem::size_of::<usize>();

                let len = haystack.len();
                let ptr = haystack.as_ptr();
                let align = (ptr as usize) & (USIZE_BYTES - 1);
                let mut i = 0;
                if align > 0 {
                    i = cmp::min(USIZE_BYTES - align, len);
                    if let Some(found) = slow(needle1, needle2, &haystack[..i]) {
                        return Some(found);
                    }
                }
                let repeated_b1 = repeat_byte(needle1);
                let repeated_b2 = repeat_byte(needle2);
                if len >= USIZE_BYTES {
                    while i <= len - USIZE_BYTES {
                        unsafe {
                            let u = *(ptr.offset(i as isize) as *const usize);
                            let found_ub1 = contains_zero_byte(u ^ repeated_b1);
                            let found_ub2 = contains_zero_byte(u ^ repeated_b2);
                            if found_ub1 || found_ub2 {
                                break;
                            }
                        }
                        i += USIZE_BYTES;
                    }
                }
                slow(needle1, needle2, &haystack[i..]).map(|pos| i + pos)
            }

            pub fn memchr3(
                needle1: u8,
                needle2: u8,
                needle3: u8,
                haystack: &[u8],
            ) -> Option<usize> {
                fn slow(b1: u8, b2: u8, b3: u8, haystack: &[u8]) -> Option<usize> {
                    haystack.iter().position(|&b| b == b1 || b == b2 || b == b3)
                }

                #[allow(non_snake_case)]
                let USIZE_BYTES: usize = mem::size_of::<usize>();

                let len = haystack.len();
                let ptr = haystack.as_ptr();
                let align = (ptr as usize) & (USIZE_BYTES - 1);
                let mut i = 0;
                if align > 0 {
                    i = cmp::min(USIZE_BYTES - align, len);
                    if let Some(found) = slow(needle1, needle2, needle3, &haystack[..i]) {
                        return Some(found);
                    }
                }
                let repeated_b1 = repeat_byte(needle1);
                let repeated_b2 = repeat_byte(needle2);
                let repeated_b3 = repeat_byte(needle3);
                if len >= USIZE_BYTES {
                    while i <= len - USIZE_BYTES {
                        unsafe {
                            let u = *(ptr.offset(i as isize) as *const usize);
                            let found_ub1 = contains_zero_byte(u ^ repeated_b1);
                            let found_ub2 = contains_zero_byte(u ^ repeated_b2);
                            let found_ub3 = contains_zero_byte(u ^ repeated_b3);
                            if found_ub1 || found_ub2 || found_ub3 {
                                break;
                            }
                        }
                        i += USIZE_BYTES;
                    }
                }
                slow(needle1, needle2, needle3, &haystack[i..]).map(|pos| i + pos)
            }
        }
    }

    pub mod num {
        use std::ops::Mul;

        pub mod prelude {
            pub use super::MulSquareExt;
        }

        pub trait MulSquareExt {
            fn square(self) -> Self;
        }

        impl<M: Mul<Output=M>+Copy> MulSquareExt for M {
            fn square(self) -> Self {
                self * self
            }
        }
    }


    pub mod io {
        use std::io;
        use std::io::*;

        use super::common::*;


        pub mod prelude {
            pub use super::BufReadScanExt;
            pub use super::stdin;
            pub use super::stdout;
        }

        pub trait BufReadScanExt {
            fn scan<T: FromByteStream>(&mut self) -> Option<Result<T>>;
            fn scan_with<P: ParseByteStream>(&mut self, parser: &mut P)
                -> Option<Result<P::Output>>;
            fn scanner<T: FromByteStream>(self) -> Scanner<Self, T::Parser>
                where Self: Sized;
            fn scanner_with<P: ParseByteStream>(self, parser: P)
                -> Scanner<Self, P>
                where Self: Sized;
            fn scanner2<T, U>(self) -> Scanner2<Self, T::Parser, U::Parser>
            where
                Self: Sized,
                T: FromByteStream,
                U: FromByteStream;
            fn scanner2_with<P1, P2>(self, parser1: P1, parser2: P2)
                -> Scanner2<Self, P1, P2>
            where
                Self: Sized,
                P1: ParseByteStream,
                P2: ParseByteStream;
            fn scanner3<T, U, V>(self)
                -> Scanner3<Self, T::Parser, U::Parser, V::Parser>
            where
                Self: Sized,
                T: FromByteStream,
                U: FromByteStream,
                V: FromByteStream;
            fn scanner3_with<P1, P2, P3>(
                self,
                parser1: P1,
                parser2: P2,
                parser3: P3,
            )
                -> Scanner3<Self, P1, P2, P3>
            where
                Self: Sized,
                P1: ParseByteStream,
                P2: ParseByteStream,
                P3: ParseByteStream;
        }

        impl<R: BufRead+?Sized> BufReadScanExt for R {
            fn scan<T>(&mut self) -> Option<Result<T>>
                where T: FromByteStream
            {
                self.scan_with(&mut T::parser())
            }

            fn scan_with<P: ParseByteStream>(&mut self, parser: &mut P)
                -> Option<Result<P::Output>>
            {
                match skip_whitespace(self) {
                    Ok(true) => return None,
                    Ok(false) => (),
                    Err(e) => return Some(Err(e)),
                }

                let mut first = true;
                loop {
                    let (consume, done) = {
                        let buf = match self.fill_buf() {
                            Ok(b) => b,
                            Err(ref e)
                                if e.kind() == ErrorKind::Interrupted =>
                            {
                                continue;
                            },
                            Err(e) => return Some(Err(e)),
                        };

                        if buf.is_empty() {
                            return if first {
                                None
                            } else {
                                Some(Ok(parser.take()))
                            };
                        }

                        let i = memchr3(b' ', b'\r', b'\n', buf)
                            .unwrap_or(buf.len());

                        parser.eat(&buf[..i]);

                        (i, i != buf.len())
                    };

                    if consume != 0 {
                        self.consume(consume);
                    }

                    if done {
                        return Some(Ok(parser.take()));
                    }

                    first = false;
                }
            }

            fn scanner<T>(self) -> Scanner<Self, T::Parser>
            where
                Self: Sized,
                T: FromByteStream
            {
                self.scanner_with(T::parser())
            }

            fn scanner_with<P>(self, parser: P) -> Scanner<Self, P>
                where Self: Sized, P: ParseByteStream
            {
                Scanner {
                    reader: self,
                    // XXX: struct field shorthands are unstable on
                    // AtCoder's current version of rustc (1.15.1).
                    parser: parser,
                }
            }

            fn scanner2<T, U>(self) -> Scanner2<Self, T::Parser, U::Parser>
            where
                Self: Sized,
                T: FromByteStream,
                U: FromByteStream,
            {
                self.scanner2_with(T::parser(), U::parser())
            }

            fn scanner2_with<P1, P2>(self, parser1: P1, parser2: P2)
                -> Scanner2<Self, P1, P2>
            where
                Self: Sized,
                P1: ParseByteStream,
                P2: ParseByteStream,
            {
                Scanner2 {
                    reader: self,
                    parser1: parser1,
                    parser2: parser2,
                }
            }

            fn scanner3<T, U, V>(self)
                -> Scanner3<Self, T::Parser, U::Parser, V::Parser>
            where
                Self: Sized,
                T: FromByteStream,
                U: FromByteStream,
                V: FromByteStream,
            {
                self.scanner3_with(T::parser(),U::parser(),V::parser())
            }

            fn scanner3_with<P1, P2, P3>(
                self,
                parser1: P1,
                parser2: P2,
                parser3: P3,
            )
                -> Scanner3<Self, P1, P2, P3>
            where
                Self: Sized,
                P1: ParseByteStream,
                P2: ParseByteStream,
                P3: ParseByteStream,
            {
                Scanner3 {
                    reader: self,
                    parser1: parser1,
                    parser2: parser2,
                    parser3: parser3,
                }
            }
        }

        fn skip_whitespace<R: BufRead+?Sized>(r: &mut R) -> Result<bool> {
            let mut found_newline = false;

            loop {
                let (consume, done) = {
                    let buf = match r.fill_buf() {
                        Ok(b) => b,
                        Err(ref e) if e.kind() == ErrorKind::Interrupted => {
                            continue;
                        },
                        Err(e) => return Err(e),
                    };

                    let mut consume = 0;
                    let mut done = false;
                    for &b in buf {
                        match b {
                            b' ' | b'\r' => (),
                            b'\n' => found_newline = true,
                            _ => {
                                done = true;
                                break;
                            },
                        }
                        consume += 1;
                    }

                    (consume, done)
                };

                if consume != 0 {
                    r.consume(consume);
                }

                if done || consume == 0 {
                    return Ok(found_newline);
                }
            }
        }

        pub struct Scanner<R, P> {
            reader: R,
            parser: P,
        }

        impl<R, P> Scanner<R, P> {
            pub fn into_inner(self) -> R {
                self.reader
            }

            pub fn switch<T: FromByteStream>(self) -> Scanner<R, T::Parser> {
                self.switch_parser(T::parser())
            }

            pub fn switch_parser<Q: ParseByteStream>(self, parser: Q)
                -> Scanner<R, Q>
            {
                Scanner {
                    reader: self.reader,
                    parser: parser,
                }
            }
        }

        impl<R: BufRead, P: ParseByteStream> Iterator for Scanner<R, P> {
            type Item = Result<P::Output>;

            fn next(&mut self) -> Option<Result<P::Output>> {
                self.reader.scan_with(&mut self.parser)
            }
        }

        macro_rules! multi_scanner {
            ($(
                pub struct $S:ident {
                    $reader:ident: $R:ident,
                    $parser1:ident: $T1:ident -> $P1:ident $Q1:ident,
                    $($parser:ident: $T:ident -> $P:ident $Q:ident,)*
                }
            )*) => {$(
                pub struct $S<$R, $P1, $($P),*> {
                    $reader: $R,
                    $parser1: $P1,
                    $($parser: $P,)*
                }

                impl<$R, $P1, $($P),*> $S<$R, $P1, $($P),*> {
                    pub fn into_inner(self) -> $R {
                        self.$reader
                    }

                    pub fn switch<$T1, $($T),*>(self)
                        -> $S<$R, $T1::Parser, $($T::Parser),*>
                    where
                        $T1: FromByteStream,
                        $($T: FromByteStream,)*
                    {
                        self.switch_parsers(T1::parser(), $($T::parser()),*)
                    }

                    pub fn switch_parsers<$Q1, $($Q),*>(
                        self,
                        $parser1: $Q1,
                        $($parser: $Q,)*
                    )
                        -> $S<$R, $Q1, $($Q),*>
                    where
                        $Q1: ParseByteStream,
                        $($Q: ParseByteStream,)*
                    {
                        $S {
                            $reader: self.$reader,
                            $parser1: $parser1,
                            $($parser: $parser,)*
                        }
                    }
                }

                impl<$R: BufRead, $P1, $($P),*> Iterator
                    for $S<$R, $P1, $($P),*>
                where
                    $P1: ParseByteStream,
                    $($P: ParseByteStream,)*
                {
                    type Item = Result<($P1::Output, $($P::Output),*)>;

                    fn next(&mut self) -> Option<Self::Item> {
                        self.$reader.scan_with(&mut self.$parser1).map(|r| {
                            let t1 = r?;
                            $(
                                // The binding's name is a dummy.
                                let $parser = self.reader
                                    .scan_with(&mut self.$parser)
                                    .expect(concat!(
                                        stringify!($S),
                                        ": missing the item for `",
                                        stringify!($parser),
                                        "`"
                                    ))?;
                            )*
                            Ok((t1, $($parser),*))
                        })
                    }
                }
            )*};
        }

        multi_scanner! {
            pub struct Scanner2 {
                reader: R,
                parser1: T1 -> P1 Q1,
                parser2: T2 -> P2 Q2,
            }

            pub struct Scanner3 {
                reader: R,
                parser1: T1 -> P1 Q1,
                parser2: T2 -> P2 Q2,
                parser3: T3 -> P3 Q3,
            }
        }


        pub fn stdin() -> LockedStdio<Stdin, StdinLock<'static>> {
            let handle = io::stdin();
            unsafe {
                LockedStdio {
                    lock: (*(&handle as *const Stdin)).lock(),
                    _handle: handle,
                }
            }
        }

        pub fn stdout() -> LockedStdio<Stdout, StdoutLock<'static>> {
            let handle = io::stdout();
            unsafe {
                LockedStdio {
                    lock: (*(&handle as *const Stdout)).lock(),
                    _handle: handle,
                }
            }
        }

        pub struct LockedStdio<H, L> {
            _handle: H,
            lock: L,
        }

        impl<H, L: Read> Read for LockedStdio<H, L> {
            fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
                Read::read(&mut self.lock, buf)
            }
        }

        impl<H, L: BufRead> BufRead for LockedStdio<H, L> {
            fn fill_buf(&mut self) -> Result<&[u8]> {
                BufRead::fill_buf(&mut self.lock)
            }

            fn consume(&mut self, amt: usize) {
                BufRead::consume(&mut self.lock, amt);
            }
        }

        impl<H, L: Write> Write for LockedStdio<H, L> {
            fn write(&mut self, buf: &[u8]) -> Result<usize> {
                Write::write(&mut self.lock, buf)
            }

            fn flush(&mut self) -> Result<()> {
                Write::flush(&mut self.lock)
            }
        }
    }

    mod ffi {
        use std::os::raw::{c_int, c_void};

        extern {
            pub fn memchr(src: *const c_void, c: c_int, n: usize)
                -> *mut c_void;
        }
    }
}

#[cfg(test)]
mod tests;

#[allow(unused_imports)]
use std::io::prelude::*;

fn main() {
    let (n, m) = stdin().scanner2::<u64, u64>().next().unwrap().unwrap();

    println!("{}", if n == 1 {
        if m == 1 { 1 } else { m-2 }
    } else if m == 1 {
        n-2
    } else {
        (m-2)*(n-2)
    })
}