#[allow(unused_imports)]
use std::cmp::{max, min, Ordering};
#[allow(unused_imports)]
use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, VecDeque};
#[allow(unused_imports)]
use std::io::{stdin, stdout, BufWriter, Write};
#[allow(unused_imports)]
use std::iter::FromIterator;
mod util {
    use std::fmt::Debug;
    use std::io::{stdin, stdout, BufWriter, StdoutLock};
    use std::str::FromStr;
    #[allow(dead_code)]
    pub fn line() -> String {
        let mut line: String = String::new();
        stdin().read_line(&mut line).unwrap();
        line.trim().to_string()
    }
    #[allow(dead_code)]
    pub fn chars() -> Vec<char> {
        line().chars().collect()
    }
    #[allow(dead_code)]
    pub fn gets<T: FromStr>() -> Vec<T>
    where
        <T as FromStr>::Err: Debug,
    {
        let mut line: String = String::new();
        stdin().read_line(&mut line).unwrap();
        line.split_whitespace()
            .map(|t| t.parse().unwrap())
            .collect()
    }
    #[allow(dead_code)]
    pub fn with_bufwriter<F: FnOnce(BufWriter<StdoutLock>) -> ()>(f: F) {
        let out = stdout();
        let writer = BufWriter::new(out.lock());
        f(writer)
    }
}
#[allow(unused_macros)]
macro_rules ! get { ( [ $ t : ty ] ) => { { let mut line : String = String :: new ( ) ; stdin ( ) . read_line ( & mut line ) . unwrap ( ) ; line . split_whitespace ( ) . map ( | t | t . parse ::<$ t > ( ) . unwrap ( ) ) . collect ::< Vec < _ >> ( ) } } ; ( [ $ t : ty ] ; $ n : expr ) => { ( 0 ..$ n ) . map ( | _ | get ! ( [ $ t ] ) ) . collect ::< Vec < _ >> ( ) } ; ( $ t : ty ) => { { let mut line : String = String :: new ( ) ; stdin ( ) . read_line ( & mut line ) . unwrap ( ) ; line . trim ( ) . parse ::<$ t > ( ) . unwrap ( ) } } ; ( $ ( $ t : ty ) ,* ) => { { let mut line : String = String :: new ( ) ; stdin ( ) . read_line ( & mut line ) . unwrap ( ) ; let mut iter = line . split_whitespace ( ) ; ( $ ( iter . next ( ) . unwrap ( ) . parse ::<$ t > ( ) . unwrap ( ) , ) * ) } } ; ( $ t : ty ; $ n : expr ) => { ( 0 ..$ n ) . map ( | _ | get ! ( $ t ) ) . collect ::< Vec < _ >> ( ) } ; ( $ ( $ t : ty ) ,*; $ n : expr ) => { ( 0 ..$ n ) . map ( | _ | get ! ( $ ( $ t ) ,* ) ) . collect ::< Vec < _ >> ( ) } ; }
#[allow(unused_macros)]
macro_rules ! debug { ( $ ( $ a : expr ) ,* ) => { eprintln ! ( concat ! ( $ ( stringify ! ( $ a ) , " = {:?}, " ) ,* ) , $ ( $ a ) ,* ) ; } }
const BIG_STACK_SIZE: bool = true;
#[allow(dead_code)]
fn main() {
    use std::thread;
    if BIG_STACK_SIZE {
        thread::Builder::new()
            .stack_size(32 * 1024 * 1024)
            .name("solve".into())
            .spawn(solve)
            .unwrap()
            .join()
            .unwrap();
    } else {
        solve();
    }
}
#[allow(dead_code)]
fn read<T>() -> Vec<T>
where T:
    std::str::FromStr,
    T::Err: std::fmt::Debug {

    let mut buf = String::new();
    stdin().read_line(&mut buf).unwrap();
    buf.split_whitespace()
        .map(|s| s.trim().parse().unwrap())
        .collect()
}

#[derive(Clone)]
struct Node {
    id: usize,
    nodes: Vec<usize>,
}

#[derive(Clone)]
struct Graph {
    nodes: Vec<Node>
}

impl Graph {
    #[allow(dead_code)]
    fn new(ab: Vec<(usize, usize)>) -> Self {
        let mut new = Graph{ nodes: Vec::new() };
        for (a, b) in ab {
            if let Some(index) = new.nodes.iter().position(|t| t.id == a) {
                new.nodes[index].nodes.push(b);
            } else {
                new.nodes.push(Node{ id: a, nodes: vec![b] });
            }

            if let Some(index) = new.nodes.iter().position(|t| t.id == b) {
                new.nodes[index].nodes.push(a);
            } else {
                new.nodes.push(Node{ id: b, nodes: vec![a] });
            }
        }
        new
    }


    #[allow(dead_code)]
    fn get(&self, id: usize) -> Option<&Node> {
        self.nodes.iter().find(|a| a.id == id)
    }


    #[allow(dead_code)]
    fn get_mut<'a>(&'a mut self, id: usize) -> Option<&'a mut Node> {
        for i in 0..self.nodes.len() {
            if self.nodes[i].id == id {
                return Some(&mut self.nodes[i]);
            }
        }
        None
    }

    #[allow(dead_code)]
    fn cut(&mut self, lid: usize, rid: usize) {
        {
            let lnode = self.get_mut(lid).unwrap();
            let li = lnode.nodes.iter().position(|a| *a == rid).unwrap();
            lnode.nodes.remove(li);
        }
        {
            let rnode = self.get_mut(rid).unwrap();
            let ri = rnode.nodes.iter().position(|a| *a == lid).unwrap();
            rnode.nodes.remove(ri);
        }
    }


    #[allow(dead_code)]
    fn is_connected(&self) -> bool {
        let first = &self.nodes[0];
        let mut queue = VecDeque::new();
        let mut exist = BTreeSet::new();

        queue.push_back(first.id);
        exist.insert(first.id);

        while !queue.is_empty() {
            let node = self.get(queue.pop_front().unwrap()).unwrap();
            for new in &node.nodes {
                if !exist.contains(&new) {
                    exist.insert(*new);
                    queue.push_back(*new);
                }
            }
        }
        exist.len() == self.nodes.len()
    }

    #[allow(dead_code)]
    fn is_connected_with(&self, lid: usize, rid: usize) -> bool {
        let first = match self.get(lid) {
            Some(node) => node,
            None => return false,
        };
        let mut queue = VecDeque::new();
        let mut exist = BTreeSet::new();

        queue.push_back(first.id);
        exist.insert(first.id);

        while !queue.is_empty() {
            let node = self.get(queue.pop_front().unwrap()).unwrap();
            for new in &node.nodes {
                if !exist.contains(&new) {
                    exist.insert(*new);
                    queue.push_back(*new);
                }
            }
        }
        exist.contains(&rid)
    }

    #[allow(dead_code)]
    fn path_num(&self, current: usize, visited: BTreeSet<usize>) -> usize {
        if visited.len() + 1 == self.nodes.len() {
            return 1;
        }
        let node = self.get(current).unwrap();
        let mut next_visited = visited.clone();
        next_visited.insert(current);
        let mut num = 0;
        for next in &node.nodes {
            if !visited.contains(next) {
                num += self.path_num(*next, next_visited.clone());
            }
        }
        num
    }
}

fn solve() {
    let (_n, m) = get!(usize, usize);
    let ab = get!(usize, usize; m);
    let graph = Graph::new(ab);

    println!("{}", graph.path_num(1, BTreeSet::new()));
}


Measured memory_bytes: 8572