use std::cmp::Ordering;
use std::fmt::Debug;
#[allow(unused_imports)]
use std::fs::File;
#[allow(unused_imports)]
use std::io::{stdin, stdout, BufReader, BufWriter, Read, Write};
use std::str::FromStr;

pub fn start<R: Read, W: Write>(input: R, output: &mut W) {
    let mut scanner = Scanner::new(input.bytes().map(Result::unwrap));
    let n = scanner.next_value::<usize>();
    let m = scanner.next_value::<usize>();
    let ab = scanner.next_2darray::<usize>(2, m);
    let mut abm = vec![vec![0_u8; n]; n];
    for u in ab {
        abm[u[0] - 1][u[1] - 1] = 1;
        abm[u[1] - 1][u[0] - 1] = 1;
    }

    let result = permutations(n - 1, n - 1)
        .flat_map(|x| {
            let mut prev = 0;
            for y in x.iter().map(move |&y| y as usize + 1) {
                if abm[prev][y] == 0 {
                    return None;
                }
                prev = y;
            }
            Some(())
        })
        .count();
    writeln!(output, "{}", result).unwrap();
}

////////////////////////////
// Tests
////////////////////////////

#[cfg(test)]
mod test {
    use super::start;
    use std::ffi::OsStr;
    use std::fs;
    use std::fs::File;
    use std::io::Cursor;
    use std::io::{BufReader, Read};

    #[test]
    fn example() {
        let mut entries = fs::read_dir("./tests")
            .unwrap()
            .into_iter()
            .flat_map(|x| x.ok())
            .map(|x| x.path())
            .filter(|x| x.extension().unwrap_or(OsStr::new("")).to_string_lossy() == "in")
            .collect::<Vec<_>>();
        entries.sort();
        for entry in entries {
            let mut output = Vec::new();
            BufReader::new(File::open(entry.with_extension("out")).unwrap())
                .read_to_end(&mut output)
                .unwrap();
            let input = BufReader::new(File::open(entry).unwrap());

            let mut out = Cursor::new(Vec::new());
            start(input, &mut out);
            let outvec = out.into_inner();
            println!("{}", String::from_utf8((&outvec).to_vec()).unwrap());
            assert_eq!(outvec, output);
        }
    }
}

////////////////////////////
// Snipetts
////////////////////////////

#[allow(dead_code)]
fn main() {
    let stdin = stdin();
    let stdout = stdout();
    let mut out = BufWriter::new(stdout.lock());
    let input = BufReader::new(stdin.lock());
    start(input, &mut out);
}

// // ファイルの場合
// #[allow(dead_code)]
// fn main() {
//     let mut out = BufWriter::new(File::create("output.txt").unwrap());
//     let input = BufReader::new(File::open("input.txt").unwrap());
//     start(input, &mut out);
// }

// 大きなスタックが必要な場合はこちらを利用する
// #[allow(dead_code)]
// fn main() {
//     std::thread::Builder::new()
//         .name("big stack size".into())
//         .stack_size(32 * 1024 * 1024) // 32 MBのスタックサイズ
//         .spawn(|| {
//             let stdin = stdin();
//             let stdout = stdout();
//             let out = BufWriter::new(stdout.lock());
//             let input = BufReader::new(stdin.lock());
//             start(input, out);
//         })
//         .unwrap()
//         .join()
//         .unwrap();
// }

pub struct Scanner<I: Iterator<Item = u8>> {
    input: I,
}

impl<I: Iterator<Item = u8>> Scanner<I> {
    pub fn new(input: I) -> Self {
        Scanner::<I> { input: input }
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    pub fn next_value<T: FromStr>(&mut self) -> T
    where
        <T as FromStr>::Err: Debug,
    {
        T::from_str(
            &(self
                .input
                .by_ref()
                .map(char::from)
                .skip_while(|x| x.is_whitespace())
                .take_while(|x| !x.is_whitespace())
                .collect::<String>()),
        )
        .unwrap()
    }

    // 文字コードはISO/IEC 8859-1 (Latin-1)とする
    #[allow(dead_code)]
    pub fn next_bytes(&mut self) -> Vec<u8> {
        self.input
            .by_ref()
            .skip_while(|&x| char::from(x).is_whitespace())
            .take_while(|&x| !char::from(x).is_whitespace())
            .collect::<Vec<_>>()
    }

    #[allow(dead_code)]
    pub fn next_array<T: FromStr>(&mut self, len: usize) -> Vec<T>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..len).map(|_| self.next_value()).collect()
    }

    #[allow(dead_code)]
    pub fn next_2darray<T: FromStr>(&mut self, x: usize, y: usize) -> Vec<Vec<T>>
    where
        <T as FromStr>::Err: Debug,
    {
        (0..y).map(|_| self.next_array(x)).collect()
    }

    #[allow(dead_code)]
    pub fn next_2d_bytes(&mut self, len: usize) -> Vec<Vec<u8>> {
        (0..len).map(|_| self.next_bytes()).collect()
    }
}

// 1.15.1でReverseを使う
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e
#[derive(Eq, PartialEq, Clone, Debug)]
pub struct Reverse<T>(pub T);

impl<T: PartialOrd> PartialOrd for Reverse<T> {
    fn partial_cmp(&self, other: &Reverse<T>) -> Option<Ordering> {
        other.0.partial_cmp(&self.0)
    }
}

impl<T: Ord> Ord for Reverse<T> {
    fn cmp(&self, other: &Reverse<T>) -> Ordering {
        other.0.cmp(&self.0)
    }
}

// PartialOrdをTotalOrdにするラッパ
// https://qiita.com/hatoo@github/items/fa14ad36a1b568d14f3e

#[derive(PartialEq, PartialOrd)]
pub struct Total<T>(pub T);

impl<T: PartialEq> Eq for Total<T> {}

impl<T: PartialOrd> Ord for Total<T> {
    fn cmp(&self, other: &Total<T>) -> Ordering {
        self.0.partial_cmp(&other.0).unwrap()
    }
}

// 順列生成
#[allow(dead_code)]
pub fn permutations(n: usize, k: usize) -> Box<Iterator<Item = Vec<u8>>> {
    fn inner(array: Vec<u8>, depth: usize, max: u8) -> Box<Iterator<Item = Vec<u8>>> {
        let na = (0..max).flat_map(move |x| {
            if array.contains(&x) {
                None
            } else {
                let mut newarray = array.clone();
                newarray.push(x);
                Some(newarray)
            }
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| inner(x, depth - 1, max)))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), k - 1, n as u8)
}

// 組み合わせ生成
#[allow(dead_code)]
pub fn combinations(n: usize, r: usize) -> Box<Iterator<Item = Vec<u8>>> {
    fn inner(
        array: Vec<u8>,
        depth: usize,
        min: usize,
        max: usize,
    ) -> Box<Iterator<Item = Vec<u8>>> {
        let na = (min..(max - depth)).map(move |x| {
            let mut newarray = array.clone();
            newarray.push(x as u8);
            newarray
        });
        if depth > 0 {
            Box::new(na.flat_map(move |x| {
                let next = usize::from(*x.last().unwrap()) + 1;
                inner(x, depth - 1, next, max)
            }))
        } else {
            Box::new(na)
        }
    }
    inner(Vec::new(), r - 1, 0, n)
}


Measured memory_bytes: 4352