import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.InputMismatchException;

/**
 * @author Egor Kulikov (egor@egork.net)
 *         Created on 14.03.2010
 */
public class TaskD implements Runnable {
	private InputReader in;
	private PrintWriter out;
	private int n;
	private int m;
	private boolean[][] e;
	private long[][] qp;

	private static class InputReader {
		private InputStream stream;
		private byte[] buf = new byte[1000];
		private int curChar, numChars;

		public InputReader(InputStream stream) {
			this.stream = stream;
		}

		private int read() {
			if (numChars == -1)
				throw new InputMismatchException();
			if (curChar >= numChars) {
				curChar = 0;
				try {
					numChars = stream.read(buf);
				} catch (IOException e) {
					throw new InputMismatchException();
				}
				if (numChars <= 0)
					return -1;
			}
			return buf[curChar++];
		}

		public int readInt() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-') {
				sgn = -1;
				c = read();
			}
			int res = 0;
			do {
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			} while (!isSpaceChar(c));
			return res * sgn;
		}

		public long readLong() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-') {
				sgn = -1;
				c = read();
			}
			long res = 0;
			do {
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			} while (!isSpaceChar(c));
			return res * sgn;
		}

		public String readString() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			StringBuffer res = new StringBuffer();
			do {
				res.appendCodePoint(c);
				c = read();
			} while (!isSpaceChar(c));
			return res.toString();
		}

		private boolean isSpaceChar(int c) {
			return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
		}

		private String readLine0() {
			StringBuffer buf = new StringBuffer();
			int c = read();
			while (c != '\n' && c != -1) {
				buf.appendCodePoint(c);
				c = read();
			}
			return buf.toString();
		}

		public String readLine() {
			String s = readLine0();
			while (s.trim().length() == 0)
				s = readLine0();
			return s;
		}

		public String readLine(boolean ignoreEmptyLines) {
			if (ignoreEmptyLines)
				return readLine();
			else
				return readLine0();
		}

		public BigInteger readBigInteger() {
			try {
				return new BigInteger(readString());
			} catch (NumberFormatException e) {
				throw new InputMismatchException();
			}
		}

		public char readCharacter() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			return (char) c;
		}

		public double readDouble() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			int sgn = 1;
			if (c == '-') {
				sgn = -1;
				c = read();
			}
			double res = 0;
			while (!isSpaceChar(c) && c != '.') {
				if (c < '0' || c > '9')
					throw new InputMismatchException();
				res *= 10;
				res += c - '0';
				c = read();
			}
			if (c == '.') {
				c = read();
				double m = 1;
				while (!isSpaceChar(c)) {
					if (c < '0' || c > '9')
						throw new InputMismatchException();
					m /= 10;
					res += (c - '0') * m;
					c = read();
				}
			}
			return res * sgn;
		}
	}

	public static void main(String[] args) {
//		new Thread(new Template()).start();
		new TaskD().run();
	}

	public TaskD() {
//		String id = getClass().getName().toLowerCase();
//		try {
//			System.setIn(new FileInputStream(id + ".in"));
//			System.setOut(new PrintStream(new FileOutputStream(id + ".out")));
//		} catch (FileNotFoundException e) {
//			throw new RuntimeException();
//		}
		in = new InputReader(System.in);
		out = new PrintWriter(System.out);
	}

	public void run() {
//        int numTests = in.readInt();
//        for (int testNumber = 0; testNumber < numTests; testNumber++) {
//        }
		n = in.readInt();
		m = in.readInt();
		e = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			int a = in.readInt() - 1;
			int b = in.readInt() - 1;
			e[a][b] = e[b][a] = true;
		}
//		n = 19;
//		e = new boolean[n][n];
//		for (int i = 0; i < n; i++) {
//			for (int j = 0; j < n; j++)
//				e[i][j] = i != j;
//		}
		int msk = (1 << n) - 1;
		qp = new long[n - 1][1 << (n - 1)];
		long ans = 0;
		for (int i = n - 1; i >= 0; i--) {
			msk -= (1 << i);
			for (int k = 0; k < i; k++)
				Arrays.fill(qp[k], 0, 1 << i, -1);
			for (int j = 0; j < i; j++) {
				if (e[i][j]) {
					e[i][j] = e[j][i] = false;
					ans += go(j, msk - (1 << j), i);
					e[i][j] = e[j][i] = true;
				}
			}
		}
		out.println(ans / 2);
		out.close();
	}

	private long go(int v, int msk, int u) {
		if (qp[v][msk] != -1)
			return qp[v][msk];
		qp[v][msk] = 0;
		if (e[v][u])
			qp[v][msk] = 1;
		for (int i = 0; i < u; i++) {
			if (e[v][i] && ((msk >> i) & 1) != 0)
				qp[v][msk] += go(i, msk - (1 << i), u);
		}
		return qp[v][msk];
	}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;

public class Main {
	public static BufferedReader in;
	public static PrintWriter out;

	public static void main(String[] args) throws IOException {
		in = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);

		boolean showLineError = true;
		if (showLineError) {
			solve();
			out.close();
		} else {
			try {
				solve();
			} catch (Exception e) {
			} finally {
				out.close();
			}
		}

	}

	static void debug(Object... os) {
		out.println(Arrays.deepToString(os));
	}

	private static void solve() throws IOException {
		String[] line = nss();
		int n = Integer.parseInt(line[0]);
		int m = Integer.parseInt(line[1]);

		boolean[][] matrix = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			line = nss();
			int u = Integer.parseInt(line[0]) - 1;
			int v = Integer.parseInt(line[1]) - 1;
			matrix[u][v] = matrix[v][u] = true;
		}
		long[][] dp = new long[1<<n][n];
		for(int i=0;i<n;i++)
			dp[1<<i][i]=1;
		long ret=0;
		for(int mask =0;mask< 1<<n;mask++){
			for(int last =0;last<n;last++)
				if((mask & (1<<last))!=0){
					int first=-1;
					for(first=0;first<n;first++)
						if((mask & (1<<first))!=0)
							break;
					for(int add =first;add<n;add++)
						if((mask & (1<<add))==0 && matrix[last][add])
							dp[mask+ (1<<add)][add]+=dp[mask][last];

					if(Long.bitCount(mask)>2 && matrix[first][last])
						ret+=dp[mask][last];
				}
		}
		out.println(ret/2L);

	}

	private static String[] nss() throws IOException {
		return in.readLine().split(" ");
	}

	private static int ni() throws NumberFormatException, IOException {
		return Integer.parseInt(in.readLine());
	}
}


import java.io.*;
import java.util.*;

public class CF11D {
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		int n = Integer.parseInt(st.nextToken());
		int m = Integer.parseInt(st.nextToken());
		boolean[][] ee = new boolean[n][n];
		while (m-- > 0) {
			st = new StringTokenizer(br.readLine());
			int i = Integer.parseInt(st.nextToken()) - 1;
			int j = Integer.parseInt(st.nextToken()) - 1;
			ee[i][j] = ee[j][i] = true;
		}
		long cnt = 0;
		// how many cycles through i with intermediate vertices before i
		for (int i = 2; i < n; i++) {
			long[][] dp = new long[1 << i][i];
			for (int j = 0; j < i; j++)
				dp[0][j] = ee[i][j] ? 1 : 0;
			// dp[b][j]: how many paths from i to j with intermediate vertices in b
			for (int b = 1; b < 1 << i; b++)
				for (int j = 0; j < i; j++) {
					if ((b & 1 << j) > 0)
						continue;
					for (int k = 0; k < i; k++) {
						if ((b & 1 << k) == 0)
							continue;
						if (ee[k][j])
							dp[b][j] += dp[b ^ 1 << k][k];
					}
					if (dp[b][j] > 0 && ee[j][i])
						cnt += dp[b][j];
				}
		}
		System.out.println(cnt / 2);
	}
}


import java.util.*;
import java.math.*;

public	class task
{
	public static void	main(String args[])
	{
		Scanner a = new Scanner(System.in);

		while(a.hasNext())
		{
			int n = a.nextInt();
			int m = a.nextInt();
			if(n == 0 && m == 0) break;

			boolean[][] adj = new boolean[n][n];
			long res = 0;

			for (int i = 0; i < m; ++i) {
				int x = a.nextInt();
				int y = a.nextInt();
				adj[x - 1][y - 1] = true;
				adj[y - 1][x - 1] = true;
			}

			for (int i = 0; i < n; ++i)
				for (int j = i + 1; j < n; ++j)
					if (adj[i][j])
						--res;

			for (int i = 0; i < n; ++i)
			{
				long[][] dp = new long[n - i][1 << (n - i)];
				dp[0][0] = 1;
				for (int mask = 0; mask < (1 << (n - i)); ++mask)
				{
					for (int j = 0; j < n - i; ++j)
					{
						if (dp[j][mask] != 0)
						{
							for (int k = 0; k < n - i; ++k)
							{
								if (((mask >> k) & 1) == 0 && adj[j + i][k + i])
									dp[k][mask | (1 << k)] += dp[j][mask];

							}
						}
					}
					if (((mask >> 0) & 1) != 0) {
						res += dp[0][mask];
					}
				}
			}
			System.out.println(res/2);
		}
	}
}


import java.util.Scanner;

public class D {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int[][]a = new int[n][n];
        for (int i = 1; i <= m; i++) {
            int v1 = sc.nextInt();
            int v2 = sc.nextInt();
            v1--;
            v2--;
            a[v1][v2] = a[v2][v1] = 1;
        }
        long[][]dp = new long[1 << n][n];
        for (int i = 0; i < n; i++) {
            dp[1 << i][i] = 1;
        }
        for (int mask = 0; mask < (1 << n); mask++) {
            if (Integer.bitCount(mask) > 1) {
                for (int i = 0; i < n; i++) {
                    if (i==Integer.numberOfTrailingZeros(mask))
                        continue;
                    if ((mask & (1 << i)) != 0) {
                        for (int j = 0; j < n; j++) {
                            if ((mask & (1 << j)) != 0 && a[j][i]==1) {
                                dp[mask][i] += dp[(mask ^ (1 << i))][j];
                            }
                        }
                    }
                }
            }
        }
        long ans = 0;
        for (int mask = 0; mask < (1 << n); mask++) {
            if (Integer.bitCount(mask) >= 3) {
                int t = Integer.numberOfTrailingZeros(mask);
                for (int i = 0; i < n; i++) {
                    if (a[t][i]==1)
                        ans += dp[mask][i];
                }
            }
        }
        ans /= 2;
        System.out.println(ans);
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.util.Arrays;

public class D11 {

	static StreamTokenizer in;
	static PrintWriter out;

	static int nextInt() throws IOException {
		in.nextToken();
		return (int)in.nval;
	}

	static String nextString() throws IOException {
		in.nextToken();
		return in.sval;
	}

	public static void main(String[] args) throws IOException {
		in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
		out = new PrintWriter(System.out);

		n = nextInt();
		m = nextInt();

		g = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			int a = nextInt()-1, b = nextInt()-1;
			g[a][b] = g[b][a] = true;
		}

		long ans = 0;
		for (int i = n; i > 0; i--) {
			//long cur = solve(i);
			long cur = calc(g, i-1);
			ans += cur;
		}

		out.println(ans/2);

		out.flush();
	}

	static int n, m, V;
	static boolean[][] g;

	/*static long solve(int V) {
		int v = V-1;
		long[][] memo = new long[V][1 << V];

		memo[v][1 << v] = 1;
		for (int mask = 1; mask < (1 << V); mask++)
			for (int i = 0; i < V; i++) if ((mask&(1 << i)) != 0)
				for (int j = 0; j < V; j++) if (g[i][j] && (mask&(1 << j)) == 0)
					memo[j][mask|(1 << j)] += memo[i][mask];

		long res = 0;
		for (int mask = 1; mask < (1 << V); mask++)
			for (int i = 0; i < V; i++)
				if (Integer.bitCount(mask) > 2 && g[v][i]) res += memo[i][mask];
		return res/2;
	}*/

	static long calc(boolean[][] bs,int n){
        long[][] dp=new long[1<<n][n];
        for(int i=0;i<n;i++){
                if(bs[i][n])
                        dp[1<<i][i] ++;
        }
        for(int i=1;i<1<<n;i++){
                for(int j=0;j<n;j++)if(((i>>j)&1)==1)
                        for(int k=0;k<n;k++)if(((i>>k)&1)==0 && bs[j][k]){//add
                                dp[i|(1<<k)][k] += dp[i][j];
                        }
        }
        long res=0;
        for(int i=0;i<1<<n;i++)for(int j=0;j<n;j++)if(Integer.bitCount(i)>=2&&bs[j][n])res+=dp[i][j];
        return res;
}
}


import java.util.*;
import java.io.*;

public class Main implements Runnable {
	private void solution() throws IOException {
		int n = in.nextInt();
		int m = in.nextInt();
		boolean[][] adj = new boolean[n][n];
		long res = 0;
		for (int i = 0; i < m; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			adj[x - 1][y - 1] = true;
			adj[y - 1][x - 1] = true;
		}
		final long[][] dp = new long[1 << n][n];
		for (int i = 0; i < n; ++i) {
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					dp[mask][j] = 0;
				}
			}
			dp[0][0] = 1;
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					if (dp[mask][j] != 0) {
						long am = dp[mask][j];
						for (int k = 0; k < n - i; ++k) {
							if (((mask >> k) & 1) == 0 && adj[j + i][k + i]) {
								dp[mask | (1 << k)][k] += am;
							}
						}
					}
				}
				if (((mask >> 0) & 1) != 0) {
					res += dp[mask][0];
				}
			}
		}
		out.println((res - m) / 2);
	}

	public void run() {
		try {
			solution();
			in.reader.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	private class Scanner {
		BufferedReader reader;
		StringTokenizer tokenizer;

		public Scanner(Reader reader) {
			this.reader = new BufferedReader(reader);
			this.tokenizer = new StringTokenizer("");
		}

		public boolean hasNext() throws IOException {
			while (!tokenizer.hasMoreTokens()) {
				String next = reader.readLine();
				if (next == null) {
					return false;
				}
				tokenizer = new StringTokenizer(next);
			}
			return true;
		}

		public String next() throws IOException {
			hasNext();
			return tokenizer.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public String nextLine() throws IOException {
			tokenizer = new StringTokenizer("");
			return reader.readLine();
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}

	public static void main(String[] args) throws IOException {
		new Thread(null, new Main(), "", 1 << 28).start();
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	Scanner in = new Scanner(new InputStreamReader(System.in));
}


import java.io.*;
import java.util.StringTokenizer;

/**
 * Created by Darren on 14-10-21.
 */
public class D {
    Reader in = new Reader(System.in);
    PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) throws IOException {
        new D().run();
    }

    int n, m;
    boolean[][] adjacency;

    void run() throws IOException {
        n = in.nextInt();
        m = in.nextInt();
        adjacency = new boolean[n+1][n];
        for (int i = 0; i < m; i++) {
            int u = in.nextInt(), v = in.nextInt();
            adjacency[u-1][v-1] = adjacency[v-1][u-1] = true;
        }

        final int MAX_MASK = (1 << n) - 1;
        long[][] dp = new long[MAX_MASK+1][n];
        for (int i = 0; i < n; i++)
            dp[1<<i][i] = 1;
        long sum = 0;
        for (int mask = 1; mask <= MAX_MASK; mask++) {
            int lowestBit = first(mask);
            for (int i = 0; i < n; i++) {
                if (bit(i, mask) && i != lowestBit) {
                    for (int j = 0; j < n; j++) {
                        if (adjacency[j][i]) {
                            dp[mask][i] += dp[mask^(1<<i)][j];
                        }
                    }
                    if (count(mask) >= 3 && adjacency[lowestBit][i])
                        sum += dp[mask][i];
                } else {
                    // dp[mask][i] = 0
                }
            }
        }

        out.println(sum / 2);
        out.flush();
    }

    int count(int mask) {
        int count = 0;
        while (mask > 0) {
            if ((mask & 1) == 1)
                count++;
            mask >>= 1;
        }
        return count;
    }

    int first(int mask) {
        int index = 0;
        while (mask > 0) {
            if ((mask & 1) == 1)
                return index;
            mask >>= 1;
            index++;
        }
        return -1;
    }

    boolean bit(int index, int mask) {
        return ((1 << index) & mask) > 0;
    }

    static class Reader {
        BufferedReader reader;
        StringTokenizer tokenizer;

        public Reader(InputStream input) {
            reader = new BufferedReader(new InputStreamReader(input));
            tokenizer = new StringTokenizer("");
        }

        /** get next word */
        String nextToken() throws IOException {
            while ( ! tokenizer.hasMoreTokens() ) {
                //TODO add check for eof if necessary
                tokenizer = new StringTokenizer( reader.readLine() );
            }
            return tokenizer.nextToken();
        }

        String readLine() throws IOException {
            return reader.readLine();
        }

        int nextInt() throws IOException {
            return Integer.parseInt( nextToken() );
        }

        long nextLong() throws IOException {
            return Long.parseLong( nextToken() );
        }

        double nextDouble() throws IOException {
            return Double.parseDouble( nextToken() );
        }
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main {

	/**
	 * @param args
	 */
	static int N, M;
	static long[] C;
	static int[][] g;
	static long[][] DP;
	public static void main(String[] args) {
		InputReader r = new InputReader(System.in);

		N = r.nextInt();
		M = r.nextInt();

		g = new int[N][N];

		C = new long[N + 1];

		DP = new long[1 << N][N];

		for(int k = 0; k < M; k++){
			int i = r.nextInt() - 1;
			int j = r.nextInt() - 1;

			g[i][j] = g[j][i] = 1;
		}

		for(long[] i : DP)
			Arrays.fill(i, -1);

		long ret = 0;
		for(int s = 0; s < N; s++){
			ret += go(s, 1 << s, s);
		}

		System.out.println(ret / 2);
	}

	private static long go(int s, int m, int e) {
		if(DP[m][e] != -1)return DP[m][e];

		long cnt = 0;
		for(int j = s; j < N; j++)if(g[e][j] == 1){
			if((m & (1 << j)) != 0){
				if(s == j && Integer.bitCount(m) >= 3){
					cnt++;
				}
			}else{
				cnt += go(s, m | 1 << j, j);
			}
		}

		return DP[m][e] = cnt;
	}

	static class InputReader {
		private BufferedReader reader;
		private StringTokenizer tokenizer;

		public InputReader(InputStream stream) {
			reader = new BufferedReader(new InputStreamReader(stream));
			tokenizer = null;
		}

		public String nextLine() {
			try {
				return reader.readLine();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				return null;
			}
		}

		public String next() {
			while (tokenizer == null || !tokenizer.hasMoreTokens()) {
				try {
					tokenizer = new StringTokenizer(reader.readLine());
				} catch (IOException e) {
					throw new RuntimeException(e);
				}
			}
			return tokenizer.nextToken();
		}

		public int nextInt() {
			return Integer.parseInt(next());
		}

		public long nextLong() {
			return Long.parseLong(next());
		}

		public double nextDouble(){
			return Double.parseDouble(next());
		}
	}
}


import java.io.*;
import java.util.*;

public class D implements Runnable {
	public static void main(String[] args) {
		new D().run();
	}

	class FastScanner {
		BufferedReader br;
		StringTokenizer st;
		boolean eof;
		String buf;

		public FastScanner(String fileName) throws FileNotFoundException {
			br = new BufferedReader(new FileReader(fileName));
			nextToken();
		}

		public FastScanner(InputStream stream) {
			br = new BufferedReader(new InputStreamReader(stream));
			nextToken();
		}

		String nextToken() {
			while (st == null || !st.hasMoreTokens()) {
				try {
					st = new StringTokenizer(br.readLine());
				} catch (Exception e) {
					eof = true;
					break;
				}
			}
			String ret = buf;
			buf = eof ? "-1" : st.nextToken();
			return ret;
		}

		int nextInt() {
			return Integer.parseInt(nextToken());
		}

		long nextLong() {
			return Long.parseLong(nextToken());
		}

		double nextDouble() {
			return Double.parseDouble(nextToken());
		}

		void close() {
			try {
				br.close();
			} catch (Exception e) {

			}
		}

		boolean isEOF() {
			return eof;
		}
	}

	FastScanner sc;
	PrintWriter out;

	public void run() {
		Locale.setDefault(Locale.US);
		try {
			sc = new FastScanner(System.in);
			out = new PrintWriter(System.out);
			solve();
			sc.close();
			out.close();
		} catch (Throwable e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	int nextInt() {
		return sc.nextInt();
	}

	String nextToken() {
		return sc.nextToken();
	}

	long nextLong() {
		return sc.nextLong();
	}

	double nextDouble() {
		return sc.nextDouble();
	}

	void solve() {
		// long time = System.currentTimeMillis();
		int n = nextInt();
		int m = nextInt();
		boolean[][] edges = new boolean[n][n];
		boolean[] edge = new boolean[(n << 5) | n];
		for (int i = 0; i < m; i++) {
			int x = nextInt() - 1;
			int y = nextInt() - 1;
			edges[x][y] = edges[y][x] = true;
			edge[(x << 5) | y] = edge[(y << 5) | x] = true;
		}
		long[][] dp = new long[n][1 << n];
		long ans = 0;
		for (int i = 0; i < n; i++) {
			for (int mask2 = 1; mask2 < 1 << (n - i - 1); ++mask2) {
				for (int j = i + 1; j < n; j++) {
					dp[j][mask2 << i << 1] = 0;
				}
			}
			for (int j = i + 1; j < n; j++) {
				if (edges[i][j]) {
					dp[j][1 << j] = 1;
				}
			}
			for (int mask2 = 1; mask2 < 1 << (n - i - 1); ++mask2) {
				int mask = (mask2 << i << 1);
				if ((mask & (mask - 1)) == 0) {
					continue;
				}
				for (int j = i + 1; j < n; j++) {
					if (((mask >> j) & 1) == 0) {
						continue;
					}
					dp[j][mask] = 0;
					for (int k = i + 1; k < n; k++) {
						if (((mask >> k) & 1) == 0 || !edge[(j << 5) | k]) {
							continue;
						}
						dp[j][mask] += dp[k][mask & ~(1 << j)];
					}
					if (edge[(i << 5) | j]) {
						ans += dp[j][mask];
					}
				}
			}
		}
//		if (n >= 3) {
//			if (edges[n - 3][n - 2] && edges[n - 3][n - 1]
//					&& edges[n - 2][n - 1]) {
//				ans += 2;
//			}
//		}
		out.println(ans / 2);
		// System.err.println(System.currentTimeMillis() - time);

		// out.println(19 + " " + 19 * 9);
		// for (int i = 0; i < 19; ++i) {
		// for (int j = i + 1; j < 19; ++j) {
		// out.println((i + 1) + " " + (j + 1));
		// }
		// }
	}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Inet4Address;
import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * Created by shirsh.bansal on 07/08/16.
 */
public class Main {
    public static void main(String[] args) throws IOException {
        FastScanner scanner = new FastScanner();

        int n = scanner.nextInt();
        int m = scanner.nextInt();

        boolean graph[][] = new boolean[n][n];
        for (int i = 0; i < m; i++) {
            int a = scanner.nextInt();
            int b = scanner.nextInt();
            graph[a-1][b-1] = true;
            graph[b-1][a-1] = true;
        }

        if(n <= 2) {
            System.out.println(0);
            return;
        }

        long dp[][] = new long[1<<n][n];

        for (int i = 0; i < (1<<n); i++) {
            Arrays.fill(dp[i], -1);
        }

        for (int i = 1; i < (1<<n); i++) {
            for (int j = 0; j < n; j++) {
                f(i, j, dp, graph, n);
            }
        }

        long sum = 0;

//        for (int i = 7; i < (1 << n); i++) {
//            if(Integer.bitCount(i) < 3) continue;
//            for (int j = 0; j < n; j++) {
//                int startNode = Integer.numberOfTrailingZeros(Integer.highestOneBit(i));
//                int endNode = j;
//
//                if(graph[startNode][endNode] && dp[i][j] != -1) {
////                    System.out.println(i + " " + startNode + " " + endNode + " " + dp[i][j]);
//                    sum += dp[i][j];
//                }
//            }
//        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < (1 << n); j++) {
                if(Integer.bitCount(j) >= 3 && graph[Integer.numberOfTrailingZeros(j)][i]) {
                    sum += dp[j][i];
                }
            }
        }

        System.out.println(sum/2);
    }

    private static long f(int mask, int i, long[][] dp, boolean[][] graph, int n) {
        if(dp[mask][i] != -1) return dp[mask][i];
        if(Integer.bitCount(mask) == 1 && (mask&(1<<i)) != 0) {
            dp[mask][i] = 1;
        } else if(Integer.bitCount(mask) > 1 && (mask&(1<<i)) != 0 && i != Integer.numberOfTrailingZeros(mask)) {
            dp[mask][i] = 0;
            for (int j = 0; j < n; j++) {
                if(graph[i][j]) dp[mask][i] = dp[mask][i] + f(mask^(1<<i), j, dp, graph, n);
            }
        } else {
            dp[mask][i] = 0;
        }

//        int tmpMask = mask ^ (1<<i);
//        for (int j = 0; j <= firstNode; j++) {
//            if(((1<<j)&tmpMask) != 0 && graph[i][j]) {
//                dp[mask][i] += f(tmpMask, j, dp, graph, n, firstNode);
//            }
//        }
//
        return dp[mask][i];
    }

    static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        FastScanner() {
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        String next() throws IOException {
            while (st == null || !st.hasMoreTokens()) {
                st = new StringTokenizer(br.readLine());
            }
            return st.nextToken();
        }

        int nextInt() throws IOException {
            return Integer.parseInt(next());
        }
    }
}


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
	InputStream fin = System.in;
	//fin = new FileInputStream("in.txt");
	Scanner cin = new Scanner(fin);

	int n = cin.nextInt();
	int m = cin.nextInt();
	int bound = 1 << n;
	boolean[][] mp = new boolean[n][n];
	long[][] dp = new long[bound][n];
	int used = 0;
	long ret = 0;
	for (int i = 0; i < n; i++) {
	    Arrays.fill(mp[i], false);
	}

	for (int i = 0; i < m; i++) {
	    int u = cin.nextInt() - 1;
	    int v = cin.nextInt() - 1;
	    mp[u][v] = mp[v][u] = true;
	}

	for (int k = 0; k < n; k++) {
	    for (int i = k; i < bound; i++) {
		Arrays.fill(dp[i], 0);
	    }
	    dp[1 << k][k] = 1;
	    for (int mask = 1 << k; mask < bound; mask++) {
		if ((mask & used) != 0)
		    continue;
		for (int i = k; i < n; i++) {
		    if (dp[mask][i] != 0) {
			if (mp[k][i] && bitcount(mask) > 2)
			    ret += dp[mask][i];
			for (int j = k; j < n; j++) {
			    if ((mask & (1 << j)) == 0 && mp[i][j]) {
				dp[mask ^ (1 << j)][j] += dp[mask][i];
			    }
			}
		    }
		}
	    }
	    used |= 1 << k;
	}

	System.out.println(ret / 2);

	fin.close();
	cin.close();
    }

    private static int bitcount(int mask) {
	// TODO Auto-generated method stub
	int ret = 0;
	while (mask > 0) {
	    ret += mask & 1;
	    mask >>= 1;
	}
	return ret;
    }
}


import java.io.*;
import java.util.*;

public class Template implements Runnable {
    private void solve() throws IOException {
        int n = nextInt();
        int m = nextInt();
        boolean[][] g = new boolean[n][n];
        for (int i = 0; i < m; ++i) {
            int a = nextInt() - 1;
            int b = nextInt() - 1;
            g[a][b] = true;
            g[b][a] = true;
        }
        /*for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)
                g[i][j] = true;*/
        long[] am = new long[n + 1];
        long[][] ways = new long[1 << n][n];
        for (int start = 0; start < n; ++start) {
            for (int mask = 0; mask < (1 << (n - start)); ++mask)
                for (int last = start; last < n; ++last) {
                    ways[mask][last - start] = 0;
                }
            ways[1][0] = 1;
            for (int mask = 0; mask < (1 << (n - start)); ++mask) {
                int cnt = 0;
                int tmp = mask;
                while (tmp > 0) {
                    ++cnt;
                    tmp = tmp & (tmp - 1);
                }
                for (int last = start; last < n; ++last)
                    if (ways[mask][last - start] > 0) {
                        long amm = ways[mask][last - start];
                        for (int i = start; i < n; ++i)
                            if ((mask & (1 << (i - start))) == 0 && g[last][i]) {
                                ways[mask | (1 << (i - start))][i - start] += amm;
                            }
                        if (g[last][start])
                            am[cnt] += ways[mask][last - start];
                    }
            }
        }
        long res = 0;
        for (int cnt = 3; cnt <= n; ++cnt) {
            if (am[cnt] % (2) != 0)
                throw new RuntimeException();
            res += am[cnt] / (2);
        }
        writer.println(res);
    }

    public static void main(String[] args) {
        new Template().run();
    }

    BufferedReader reader;
    StringTokenizer tokenizer;
    PrintWriter writer;

    public void run() {
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            tokenizer = null;
            writer = new PrintWriter(System.out);
            solve();
            reader.close();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    int nextInt() throws IOException {
        return Integer.parseInt(nextToken());
    }

    long nextLong() throws IOException {
        return Long.parseLong(nextToken());
    }

    double nextDouble() throws IOException {
        return Double.parseDouble(nextToken());
    }

    String nextToken() throws IOException {
        while (tokenizer == null || !tokenizer.hasMoreTokens()) {
            tokenizer = new StringTokenizer(reader.readLine());
        }
        return tokenizer.nextToken();
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.BufferedWriter;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.ArrayList;
import java.io.InputStream;
/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Alex
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }
    static class TaskD {
        int n;
        ArrayList<Integer>[] adj;
        long[] mem;
        long cycles(int cur, int start, int visited) {
            if (cur == start && visited > 0) {
                return Integer.bitCount(visited) >= 3 ? 1 : 0;
            }
            int index = n * visited + cur;
            if (mem[index] != -1) return mem[index];
            long res = 0;
            int newvisited = visited | (1 << cur);
            for (int nxt : adj[cur]) {
                if (nxt >= start && (nxt == start || ((visited >> nxt) & 1) == 0)) {
                    res += cycles(nxt, start, newvisited);
                }
            }
            return mem[index] = res;
        }
        public void solve(int testNumber, InputReader in, OutputWriter out) {
            n = in.readInt();
            int m = in.readInt();
            adj = new ArrayList[n];
            mem = new long[n * (1 << n)];
            for (int i = 0; i < adj.length; i++) adj[i] = new ArrayList<>();
            for (int i = 0; i < m; i++) {
                int a = in.readInt() - 1, b = in.readInt() - 1;
                adj[a].add(b);
                adj[b].add(a);
            }
            long res = 0;
            for (int start = 0; start < n; start++) {
                Arrays.fill(mem, -1);
                res += cycles(start, start, 0) / 2;
            }
            out.printLine(res);
        }
    }
    static class OutputWriter {
        private final PrintWriter writer;
        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }
        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }
        public void close() {
            writer.close();
        }
        public void printLine(long i) {
            writer.println(i);
        }
    }
    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;
        public InputReader(InputStream stream) {
            this.stream = stream;
        }
        public int read() {
            if (numChars == -1)
                throw new InputMismatchException();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }
        public int readInt() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }
        public boolean isSpaceChar(int c) {
            if (filter != null)
                return filter.isSpaceChar(c);
            return isWhitespace(c);
        }
        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }
        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);
        }
    }
}


import java.util.*;
import java.io.*;
public class ASimpleTask
{

	/************************ SOLUTION STARTS HERE ***********************/

	private static void solve(FastScanner s1, PrintWriter out){

		int V = s1.nextInt();
		int E = s1.nextInt();
		int graph[] = new int[V];
		long DP[][] = new long[1 << V][V];

		while(E-->0) {
			int u = s1.nextInt() - 1;
			int v = s1.nextInt() - 1;
			graph[u] |= (1 << v);
			graph[v] |= (1 << u);
		}

		for(int i=0;i<V;i++)
			DP[1 << i][i] = 1;

		for(int mask = 1 , end = 1 << V;mask < end;mask++) {
			for(int set = mask;Integer.bitCount(set) > 1;set ^= Integer.highestOneBit(set)) {
				int u = Integer.numberOfTrailingZeros(Integer.highestOneBit(set));
				for(int fromSet = mask ^ (1 << u);fromSet > 0; fromSet ^= Integer.lowestOneBit(fromSet)) {
					int v = Integer.numberOfTrailingZeros(fromSet);
					// System.out.printf("mask = %s , u = %d , v = %d\n" , Integer.toBinaryString(mask) , u , v);
					if((graph[u] & (1 << v)) != 0)
						DP[mask][u] += DP[mask ^ (1 << u)][v];

				}
			}
		}

		long totalCycles = 0;
		for(int mask = 1 , end = 1 << V;mask < end;mask++) {
			if(Integer.bitCount(mask) >= 3) {
				int start = Integer.numberOfTrailingZeros(mask);
				for(int set = mask;Integer.bitCount(set) > 1;set ^= Integer.highestOneBit(set)) {
					int u = Integer.numberOfTrailingZeros(Integer.highestOneBit(set));
					if((graph[u] & (1 << start)) != 0)
						totalCycles += DP[mask][u];
				}
			}
		}

		totalCycles /= 2;
/*		for(long l[] : DP)
			out.println(Arrays.toString(l));*/
		out.println(totalCycles);
	}

	/************************ SOLUTION ENDS HERE ************************/

	/************************ TEMPLATE STARTS HERE *********************/

	public static void main(String []args) throws IOException {
		FastScanner in  = new FastScanner(System.in);
		PrintWriter out =
				new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)), false);
		solve(in, out);
		in.close();
		out.close();
	}

	static class FastScanner{
		BufferedReader reader;
		StringTokenizer st;
		FastScanner(InputStream stream){reader=new BufferedReader(new InputStreamReader(stream));st=null;}
		String next()
		{while(st == null || !st.hasMoreTokens()){try{String line = reader.readLine();if(line == null){return null;}
		st = new StringTokenizer(line);}catch (Exception e){throw new RuntimeException();}}return st.nextToken();}
		String nextLine()  {String s=null;try{s=reader.readLine();}catch(IOException e){e.printStackTrace();}return s;}
		int    nextInt()   {return Integer.parseInt(next());}
		long   nextLong()  {return Long.parseLong(next());}
		double nextDouble(){return Double.parseDouble(next());}
		char   nextChar()  {return next().charAt(0);}
		int[]  nextIntArray(int n)         {int[] a= new int[n];   int i=0;while(i<n){a[i++]=nextInt();}  return a;}
		long[] nextLongArray(int n)        {long[]a= new long[n];  int i=0;while(i<n){a[i++]=nextLong();} return a;}
		int[]  nextIntArrayOneBased(int n) {int[] a= new int[n+1]; int i=1;while(i<=n){a[i++]=nextInt();} return a;}
		long[] nextLongArrayOneBased(int n){long[]a= new long[n+1];int i=1;while(i<=n){a[i++]=nextLong();}return a;}
		void   close(){try{reader.close();}catch(IOException e){e.printStackTrace();}}
	}

	/************************ TEMPLATE ENDS HERE ************************/
}


import java.util.*;
import java.io.*;
public class ASimpleTask
{

	/************************ SOLUTION STARTS HERE ***********************/

	static long memo[][];
	static int graph[];
	static long hamiltonianPath(int mask , int u) {
		if(memo[mask][u] != -1)
			return memo[mask][u];
		else if(u == Integer.numberOfTrailingZeros(mask))
			return 0;
		else {
			long sum = 0;
			for(int fromSet = mask ^ (1 << u);fromSet > 0; fromSet ^= Integer.lowestOneBit(fromSet)) {
				int v = Integer.numberOfTrailingZeros(fromSet);
				// System.out.printf("mask = %s , u = %d , v = %d\n" , Integer.toBinaryString(mask) , u , v);
				if((graph[u] & (1 << v)) != 0)
					sum += hamiltonianPath(mask ^ (1 << u), v);
			}

			return memo[mask][u] = sum;
		}
	}

	private static void solveBottomUp(FastScanner s1, PrintWriter out){

		int V = s1.nextInt();
		int E = s1.nextInt();
		graph = new int[V];
		long DP[][] = new long[1 << V][V];

		while(E-->0) {
			int u = s1.nextInt() - 1;
			int v = s1.nextInt() - 1;
			graph[u] |= (1 << v);
			graph[v] |= (1 << u);
		}

		for(int i=0;i<V;i++)
			DP[1 << i][i] = 1;

		for(int mask = 1 , end = 1 << V;mask < end;mask++) {
			for(int set = mask;Integer.bitCount(set) > 1;set ^= Integer.highestOneBit(set)) {
				int u = Integer.numberOfTrailingZeros(Integer.highestOneBit(set));
				for(int fromSet = mask ^ (1 << u);fromSet > 0; fromSet ^= Integer.lowestOneBit(fromSet)) {
					int v = Integer.numberOfTrailingZeros(fromSet);
					// System.out.printf("mask = %s , u = %d , v = %d\n" , Integer.toBinaryString(mask) , u , v);
					if((graph[u] & (1 << v)) != 0)
						DP[mask][u] += DP[mask ^ (1 << u)][v];

				}
			}
		}

		long totalCycles = 0;
		for(int mask = 1 , end = 1 << V;mask < end;mask++) {
			if(Integer.bitCount(mask) >= 3) {
				int start = Integer.numberOfTrailingZeros(mask);
				for(int set = mask;Integer.bitCount(set) > 1;set ^= Integer.highestOneBit(set)) {
					int u = Integer.numberOfTrailingZeros(Integer.highestOneBit(set));
					if((graph[u] & (1 << start)) != 0)
						totalCycles += DP[mask][u];
				}
			}
		}

		totalCycles /= 2;
/*		for(long l[] : DP)
			out.println(Arrays.toString(l));*/
		out.println(totalCycles);
	}

	private static void solveTopDown(FastScanner s1, PrintWriter out){

		int V = s1.nextInt();
		int E = s1.nextInt();
		graph = new int[V];
		memo = new long[1 << V][V];

		for(long l[] : memo)
			Arrays.fill(l, -1);

		while(E-->0) {
			int u = s1.nextInt() - 1;
			int v = s1.nextInt() - 1;
			graph[u] |= (1 << v);
			graph[v] |= (1 << u);
		}

		for(int i=0;i<V;i++)
			memo[1 << i][i] = 1;

		long totalCycles = 0;
		for(int mask = 1 , end = 1 << V;mask < end;mask++) {
			if(Integer.bitCount(mask) >= 3) {
				int start = Integer.numberOfTrailingZeros(mask);
				for(int set = mask;Integer.bitCount(set) > 1;set ^= Integer.highestOneBit(set)) {
					int u = Integer.numberOfTrailingZeros(Integer.highestOneBit(set));
					if((graph[u] & (1 << start)) != 0)
						totalCycles += hamiltonianPath(mask, u);
				}
			}
		}
		totalCycles /= 2;

		out.println(totalCycles);
	}

	/************************ SOLUTION ENDS HERE ************************/

	/************************ TEMPLATE STARTS HERE *********************/

	public static void main(String []args) throws IOException {
		FastScanner in  = new FastScanner(System.in);
		PrintWriter out =
				new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)), false);
		solveTopDown(in, out);
		in.close();
		out.close();
	}

	static class FastScanner{
		BufferedReader reader;
		StringTokenizer st;
		FastScanner(InputStream stream){reader=new BufferedReader(new InputStreamReader(stream));st=null;}
		String next()
		{while(st == null || !st.hasMoreTokens()){try{String line = reader.readLine();if(line == null){return null;}
		st = new StringTokenizer(line);}catch (Exception e){throw new RuntimeException();}}return st.nextToken();}
		String nextLine()  {String s=null;try{s=reader.readLine();}catch(IOException e){e.printStackTrace();}return s;}
		int    nextInt()   {return Integer.parseInt(next());}
		long   nextLong()  {return Long.parseLong(next());}
		double nextDouble(){return Double.parseDouble(next());}
		char   nextChar()  {return next().charAt(0);}
		int[]  nextIntArray(int n)         {int[] a= new int[n];   int i=0;while(i<n){a[i++]=nextInt();}  return a;}
		long[] nextLongArray(int n)        {long[]a= new long[n];  int i=0;while(i<n){a[i++]=nextLong();} return a;}
		int[]  nextIntArrayOneBased(int n) {int[] a= new int[n+1]; int i=1;while(i<=n){a[i++]=nextInt();} return a;}
		long[] nextLongArrayOneBased(int n){long[]a= new long[n+1];int i=1;while(i<=n){a[i++]=nextLong();}return a;}
		void   close(){try{reader.close();}catch(IOException e){e.printStackTrace();}}
	}

	/************************ TEMPLATE ENDS HERE ************************/
}


import java.io.PrintWriter;
import java.util.Scanner;

public class D {

    public void run(Scanner in, PrintWriter out) {
        int n = in.nextInt();
        int[][] graph = new int[n][n];
        int m = in.nextInt();
        for (int i = 0; i < m; i++) {
            int x = in.nextInt() - 1;
            int y = in.nextInt() - 1;
            graph[x][y] = 1;
            graph[y][x] = 1;
        }
        long[][] dyn = new long[1 << n][n];
        for (int i = 0; i < n; i++) {
            dyn[1 << i][i] = 1;
        }
        long answer = 0;
        for (int mask = 1; mask < 1 << n; mask++) {
            int start = Integer.numberOfTrailingZeros(mask);
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) == 0) continue;
                for (int j = start + 1; j < n; j++) {
                    if (graph[i][j] > 0 && (mask & (1 << j)) == 0) {
                        dyn[mask + (1 << j)][j] += dyn[mask][i];
                    }
                }
                if (graph[i][start] > 0) {
                    answer += dyn[mask][i];
                }
            }
        }
        out.println((answer - m)/ 2);
    }

    public static void main(String[] args) {
        try (Scanner in = new Scanner(System.in);
             PrintWriter out = new PrintWriter(System.out)
        ) {
            new D().run(in, out);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


import java.util.*;
import java.io.*;

public class Main implements Runnable {
	private void solution() throws IOException {
		int n = in.nextInt();
		int m = in.nextInt();
		boolean[][] adj = new boolean[n][n];
		long res = 0;
		for (int i = 0; i < m; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			adj[x - 1][y - 1] = true;
			adj[y - 1][x - 1] = true;
		}
		for (int i = 0; i < n; ++i) {
			for (int j = i + 1; j < n; ++j) {
				if (adj[i][j]) {
					--res;
				}
			}
		}
		long[][] dp = new long[1 << n][n];
		for (int i = 0; i < n; ++i) {
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					dp[mask][j] = 0;
				}
			}
			dp[0][0] = 1;
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					if (dp[mask][j] != 0) {
						for (int k = 0; k < n - i; ++k) {
							if (((mask >> k) & 1) == 0 && adj[j + i][k + i]) {
								dp[mask | (1 << k)][k] += dp[mask][j];
							}
						}
					}
				}
				if (((mask >> 0) & 1) != 0) {
					res += dp[mask][0];
				}
			}
		}
		out.println(res / 2);
	}

	public void run() {
		try {
			solution();
			in.reader.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	private class Scanner {
		BufferedReader reader;
		StringTokenizer tokenizer;

		public Scanner(Reader reader) {
			this.reader = new BufferedReader(reader);
			this.tokenizer = new StringTokenizer("");
		}

		public boolean hasNext() throws IOException {
			while (!tokenizer.hasMoreTokens()) {
				String next = reader.readLine();
				if (next == null) {
					return false;
				}
				tokenizer = new StringTokenizer(next);
			}
			return true;
		}

		public String next() throws IOException {
			hasNext();
			return tokenizer.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public String nextLine() throws IOException {
			tokenizer = new StringTokenizer("");
			return reader.readLine();
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}

	public static void main(String[] args) throws IOException {
		new Thread(null, new Main(), "", 1 << 28).start();
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	Scanner in = new Scanner(new InputStreamReader(System.in));
}


import java.util.*;
import java.io.*;

public class Main implements Runnable {
	private void solution() throws IOException {
		int n = in.nextInt();
		int m = in.nextInt();
		boolean[][] adj = new boolean[n][n];
		long res = 0;
		for (int i = 0; i < m; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			adj[x - 1][y - 1] = true;
			adj[y - 1][x - 1] = true;
		}
		final long[][] dp = new long[1 << n][n];
		for (int i = 0; i < n; ++i) {
			int Limit = 1 << (n - i);
			int nn = n - i;
			for (int mask = 0; mask < Limit; ++mask) {
				for (int j = 0; j < nn; ++j) {
					dp[mask][j] = 0;
				}
			}
			dp[0][0] = 1;
			for (int mask = 0; mask < Limit; ++mask) {
				if ((mask & 1) == 0) {
					for (int j = 0; j < nn; ++j) {
						if (dp[mask][j] != 0) {
							long am = dp[mask][j];
							for (int k = 0; k < nn; ++k) {
								if (((mask >> k) & 1) == 0 && adj[j + i][k + i]) {
									dp[mask | (1 << k)][k] += am;
								}
							}
						}
					}
				} else {
					res += dp[mask][0];
				}
			}
		}
		out.println((res - m) / 2);
	}

	public void run() {
		try {
			solution();
			in.reader.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	private class Scanner {
		BufferedReader reader;
		StringTokenizer tokenizer;

		public Scanner(Reader reader) {
			this.reader = new BufferedReader(reader);
			this.tokenizer = new StringTokenizer("");
		}

		public boolean hasNext() throws IOException {
			while (!tokenizer.hasMoreTokens()) {
				String next = reader.readLine();
				if (next == null) {
					return false;
				}
				tokenizer = new StringTokenizer(next);
			}
			return true;
		}

		public String next() throws IOException {
			hasNext();
			return tokenizer.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public String nextLine() throws IOException {
			tokenizer = new StringTokenizer("");
			return reader.readLine();
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}

	public static void main(String[] args) throws IOException {
		new Thread(null, new Main(), "", 1 << 28).start();
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	Scanner in = new Scanner(new InputStreamReader(System.in));
}


import java.io.*;
import java.util.StringTokenizer;

/**
 * Codeforces 11D - A Simple Task
 * Created by Darren on 14-10-21.
 * O(2^n * n^2) time and O(2^n * n) space.
 *
 * Tag: dynamic programming, bitmask, graph
 */
public class D {
    Reader in = new Reader(System.in);
    PrintWriter out = new PrintWriter(System.out);

    public static void main(String[] args) throws IOException {
        new D().run();
    }

    int n, m;
    boolean[][] adjacency;  // Adjacency matrix

    void run() throws IOException {
        n = in.nextInt();
        m = in.nextInt();
        adjacency = new boolean[n+1][n];
        for (int i = 0; i < m; i++) {
            int u = in.nextInt(), v = in.nextInt();
            adjacency[u-1][v-1] = adjacency[v-1][u-1] = true;   // Converted to 0-based
        }

        final int MASK_BOUND = (1 << n);

        // dp[i][j]: the number of Hamiltonian walks over the subgraph formed by
        // the mask i, starting at the smallest vertex and ending at vertex j
        // dp[1<<i][i] = 1;
        // dp[i][j] = sum_k{dp[i^j][k]} if j is within the mask i and (k,j) is an edge
        long[][] dp = new long[MASK_BOUND][n];
        for (int i = 0; i < n; i++)
            dp[1<<i][i] = 1;

        long sum = 0;
        for (int mask = 1; mask < MASK_BOUND; mask++) {
            int lowestVertex = (int) (Math.log(lowest(mask)) / Math.log(2));
            for (int i = 0; i < n; i++) {
                if (bit(i, mask) && i != lowestVertex) {
                    for (int j = 0; j < n; j++) {
                        if (adjacency[j][i]) {
                            dp[mask][i] += dp[mask^(1<<i)][j];
                        }
                    }

                    // A simple cycle with length not smaller than 3
                    if (count(mask) >= 3 && adjacency[lowestVertex][i])
                        sum += dp[mask][i];
                } else {
                    // dp[mask][i] = 0
                }
            }
        }

        out.println(sum / 2);   // A cycle is counted twice
        out.flush();
    }

    // Return the number of '1's in the binary representation of the mask
    int count(int mask) {
        int count = 0;
        while (mask > 0) {
            if ((mask & 1) == 1)
                count++;
            mask >>= 1;
        }
        return count;
    }

    // Return an integer with only one '1' in its binary form and the position of the
    // only '1' is the same with the lowest '1' in mask
    int lowest(int mask) {
        // mask = x1b where b is a sequence of zeros;
        // -mask = x'1b', where x' and b' is formed by reversing digits in x and b;
        // mask & (-mask) = 0...010...0, where the only 1 is the lowest 1 in mask
        return mask & (-mask);
    }

    // Check whether the digit at the given index of the mask is '1'
    boolean bit(int index, int mask) {
        return ((1 << index) & mask) > 0;
    }

    static class Reader {
        BufferedReader reader;
        StringTokenizer tokenizer;

        public Reader(InputStream input) {
            reader = new BufferedReader(new InputStreamReader(input));
            tokenizer = new StringTokenizer("");
        }

        /** get next word */
        String nextToken() throws IOException {
            while ( ! tokenizer.hasMoreTokens() ) {
                //TODO add check for eof if necessary
                tokenizer = new StringTokenizer( reader.readLine() );
            }
            return tokenizer.nextToken();
        }

        String readLine() throws IOException {
            return reader.readLine();
        }

        int nextInt() throws IOException {
            return Integer.parseInt( nextToken() );
        }

        long nextLong() throws IOException {
            return Long.parseLong( nextToken() );
        }

        double nextDouble() throws IOException {
            return Double.parseDouble( nextToken() );
        }
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.BufferedWriter;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.io.InputStream;
/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Alex
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }
    static class TaskD {
        int n;
        boolean[][] adj;
        long[] mem;
        int start;
        long cycles(int cur, int visited) {
            if (cur == start && visited > 0) {
                return Integer.bitCount(visited) >= 3 ? 1 : 0;
            }
            int index = visited * n + cur;
            if (mem[index] != -1) return mem[index];
            long res = 0;
            int newvisited = visited | (1 << cur);
            for (int nxt = 0; nxt < n; nxt++)
                if (adj[cur][nxt]) {
                    if (nxt >= start && (nxt == start || ((visited >> nxt) & 1) == 0)) {
                        res += cycles(nxt, newvisited);
                    }
                }
            return mem[index] = res;
        }
        public void solve(int testNumber, InputReader in, OutputWriter out) {
            n = in.readInt();
            int m = in.readInt();
            mem = new long[n * (1 << n)];
            adj = new boolean[n][n];
            for (int i = 0; i < m; i++) {
                int a = in.readInt() - 1, b = in.readInt() - 1;
                adj[a][b] = true;
                adj[b][a] = true;
            }
            long res = 0;
            for (int start = 0; start < n; start++) {
                Arrays.fill(mem, -1);
                this.start = start;
                res += cycles(start, 0) / 2;
            }
            out.printLine(res);
        }
    }
    static class OutputWriter {
        private final PrintWriter writer;
        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }
        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }
        public void close() {
            writer.close();
        }
        public void printLine(long i) {
            writer.println(i);
        }
    }
    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;
        public InputReader(InputStream stream) {
            this.stream = stream;
        }
        public int read() {
            if (numChars == -1)
                throw new InputMismatchException();
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0)
                    return -1;
            }
            return buf[curChar++];
        }
        public int readInt() {
            int c = read();
            while (isSpaceChar(c))
                c = read();
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9')
                    throw new InputMismatchException();
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }
        public boolean isSpaceChar(int c) {
            if (filter != null)
                return filter.isSpaceChar(c);
            return isWhitespace(c);
        }
        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }
        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);
        }
    }
}


import java.io.*;
import java.util.*;
public class D11 {
	static boolean[][] g;
	static int n, m;
public static void main(String[] args) throws IOException {
	input.init(System.in);
	PrintWriter out = new PrintWriter(System.out);
	n = input.nextInt();
	m = input.nextInt();
	g = new boolean[n][n];
	for(int i = 0; i<m; i++)
	{
		int a = input.nextInt()-1, b = input.nextInt()-1;
		g[a][b] = g[b][a] = true;
	}
	long res = 0;
	map = new HashMap<Integer, Long>();
	for(int i = n-1; i>=0; i--)
	{
		memo = new long[i+1][1<<(i+1)];
		for(long[] A : memo) Arrays.fill(A, -1);
		res += count(i, i, 1<<i)/2;
	}
	out.println(res);
	out.close();
}
static long[][] memo;
static HashMap<Integer, Long> map;
static long count(int at, int start, int mask)
{
	if(memo[at][mask] != -1) return memo[at][mask];
	int bits = Integer.bitCount(mask);
	if(at == start && bits > 2) return 1;
	long res = 0;
	for(int i = 0; i<=start; i++)
	{
		if(!g[at][i]) continue;
		if(i != start && (mask & (1<<i)) > 0) continue;
		if(i == start && bits <= 2) continue;
		res += count(i, start, mask | (1<<i));
	}
	return memo[at][mask] = res;
}

public static class input {
	static BufferedReader reader;
	static StringTokenizer tokenizer;

	static void init(InputStream input) {
		reader = new BufferedReader(new InputStreamReader(input));
		tokenizer = new StringTokenizer("");
	}

	static String next() throws IOException {
		while (!tokenizer.hasMoreTokens())
			tokenizer = new StringTokenizer(reader.readLine());
		return tokenizer.nextToken();
	}

	static int nextInt() throws IOException {
		return Integer.parseInt(next());
	}

	static double nextDouble() throws IOException {
		return Double.parseDouble(next());
	}

	static long nextLong() throws IOException {
		return Long.parseLong(next());
	}
}
}


import java.util.*;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		while ( sc.hasNextInt() ) {
			int n = sc.nextInt();
			long m = sc.nextInt();
			boolean edge[][] = new boolean[n][n];
			long dp[][] = new long[1<<n][n];
			for ( long i = 1 ; i<=m ; ++i ) {
	 			int u = sc.nextInt();
				int v = sc.nextInt();
				-- u;
				-- v;
				edge[u][v] = edge[v][u] = true;
			}
			for ( int i = 0 ; i<n ; ++i ) {
				dp[1<<i][i] = 1;
			}
			long res = 0;
			for ( int i = 1 ; i<(1<<n) ; ++i ) {
				int first = cal(i);
				for ( int j = first ; j<n ; ++j ) {
					if ( dp[i][j]==0 ) {
						continue;
					}
					for ( int k = first ; k<n ; ++k ) {
 						if ( j==k || !edge[j][k] ) {
							continue;
						}
						if ( k==first && judge(i) ) {
							res += dp[i][j];
						}
						if ( (i&(1<<k))==0 ) {
							dp[i|(1<<k)][k] += dp[i][j];
						}
					}
				}
			}
			System.out.println(res/2);
		}
	}
	public static int cal( int x ) {
		int ord = 0;
		while ( true ) {
			if ( (x&(1<<ord))!=0 ) {
				break;
			}
			++ ord;
		}
		return ord;
	}
	public static boolean judge( int x ) {
		int cnt = 0;
		while ( x>=1 ) {
			if ( (x&1)==1 ) {
				++ cnt;
			}
			x >>= 1;
		}
		return cnt >= 3;
	}
}


import java.util.Scanner;

public class Hello {
		public static void main(String[] args){
			Scanner scan = new Scanner(System.in);
			int n = scan.nextInt();
			int m = scan.nextInt();
			boolean[][] graph = new boolean[n][n];

			for(int i = 0; i < m; i++) {
				int from = scan.nextInt() - 1;
				int to = scan.nextInt() - 1;
				graph[from][to] = graph[to][from] = true;
			}

			int max = 1 << n;
			long[][] dp = new long[max][n];

			for(int mask = 1; mask < max; mask++) {
				for(int i = 0; i < n; i++) {
					boolean existI = (mask & (1 << i)) > 0;
					if(Integer.bitCount(mask) == 1 && existI) {
						dp[mask][i] = 1;
					} else if(Integer.bitCount(mask) > 1 && existI && first(mask) != i) {
						long sum = 0;
						for(int j = 0; j < n; j++) {
							if(graph[i][j]) sum += dp[mask ^ (1 << i)][j];
						}
						dp[mask][i] = sum;
					}
				}
			}

			long countCycles = 0;
			for(int mask = 7; mask < max; mask++) {
				for(int i = 0; i < n; i++) {
					if(Integer.bitCount(mask) >= 3 && graph[first(mask)][i]) {
						countCycles += dp[mask][i];
					}
				}
			}
			System.out.println(countCycles / 2);
		}

		public static int first(int mask) {
			int i = 0;
			while((mask & (1 << i++)) == 0);
			return i - 1;
		}
}


/**
 * Codeforces Beta Round #10
 *
 * @author ProjectYoung
 */

import java.util.Scanner;

public class CF11D {
  public static void main(String[] args) {
    Scanner sc = new Scanner(System.in);
    int n = sc.nextInt();
    int m = sc.nextInt();
    boolean[][] map = new boolean[n][n];
    long[][] dp = new long[1 << n][n];
    for (int i = 0; i < m; i++) {
      int a = sc.nextInt() - 1;
      int b = sc.nextInt() - 1;
      map[a][b] = map[b][a] = true;
      dp[(1 << a) + (1 << b)][Math.max(a, b)] = 1;
    }
    long ans = 0;
    for (int mask = 1; mask < (1 << n); mask++) {
      int lowbit = 0;
      for (; (mask & (1 << lowbit)) == 0; lowbit++);
      for (int i = lowbit + 1; i < n; i++) {
        if ((mask & (1 << i)) == 0) {
          continue;
        }
        for (int j = lowbit + 1; j < n; j++) {
          if ((mask & (1 << j)) == 0 || j == i) {
            continue;
          }
          if (map[i][j]) {
            dp[mask][i] += dp[mask ^ (1 << i)][j];
          }
        }
        if (map[lowbit][i]) {
          ans += dp[mask][i];
        }
      }
    }
    System.out.println((ans - m) / 2);
    sc.close();
  }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

public class D11 {

	static StreamTokenizer in;
	static PrintWriter out;

	static int nextInt() throws IOException {
		in.nextToken();
		return (int)in.nval;
	}

	static String nextString() throws IOException {
		in.nextToken();
		return in.sval;
	}

	public static void main(String[] args) throws IOException {
		in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
		out = new PrintWriter(System.out);

		n = nextInt();
		m = nextInt();

		g = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			int a = nextInt()-1, b = nextInt()-1;
			g[a][b] = g[b][a] = true;
		}

		long ans = 0;
		for (int i = n-1; i >= 0; i--) {
			long cur = solve(i);
			ans += cur;
		}

		out.println(ans/2);

		out.flush();
	}

	static int n, m;
	static boolean[][] g;

	static long solve(int v) {
		long[][] memo = new long[v][1 << v];

		for (int i = 0; i < v; i++)
			if (g[v][i])
				memo[i][1 << i] = 1;

		for (int mask = 1; mask < (1 << v); mask++)
			for (int i = 0; i < v; i++) if ((mask&(1 << i)) != 0)
				for (int j = 0; j < v; j++) if (g[i][j] && (mask&(1 << j)) == 0)
					memo[j][mask|(1 << j)] += memo[i][mask];

		long res = 0;
		for (int mask = 1; mask < (1 << v); mask++)
			for (int i = 0; i < v; i++)
				if (Integer.bitCount(mask) > 1 && g[v][i]) res += memo[i][mask];
		return res;
	}
}


import java.util.*;
import java.io.*;

public class Main implements Runnable {
	private void solution() throws IOException {
		int n = in.nextInt();
		int m = in.nextInt();
		boolean[][] adj = new boolean[n][n];
		long res = 0;
		for (int i = 0; i < m; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			adj[x - 1][y - 1] = true;
			adj[y - 1][x - 1] = true;
		}
		for (int i = 0; i < n; ++i) {
			for (int j = i + 1; j < n; ++j) {
				if (adj[i][j]) {
					--res;
				}
			}
		}
		for (int i = 0; i < n; ++i) {
			long[][] dp = new long[n - i][1 << (n - i)];
			dp[0][0] = 1;
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					if (dp[j][mask] != 0) {
						for (int k = 0; k < n - i; ++k) {
							if (((mask >> k) & 1) == 0 && adj[j + i][k + i]) {
								dp[k][mask | (1 << k)] += dp[j][mask];
							}
						}
					}
				}
				if (((mask >> 0) & 1) != 0) {
					res += dp[0][mask];
				}
			}
		}
		out.println(res / 2);
	}

	public void run() {
		try {
			solution();
			in.reader.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	private class Scanner {
		BufferedReader reader;
		StringTokenizer tokenizer;

		public Scanner(Reader reader) {
			this.reader = new BufferedReader(reader);
			this.tokenizer = new StringTokenizer("");
		}

		public boolean hasNext() throws IOException {
			while (!tokenizer.hasMoreTokens()) {
				String next = reader.readLine();
				if (next == null) {
					return false;
				}
				tokenizer = new StringTokenizer(next);
			}
			return true;
		}

		public String next() throws IOException {
			hasNext();
			return tokenizer.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public String nextLine() throws IOException {
			tokenizer = new StringTokenizer("");
			return reader.readLine();
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}

	public static void main(String[] args) throws IOException {
		new Thread(null, new Main(), "", 1 << 28).start();
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	Scanner in = new Scanner(new InputStreamReader(System.in));
}


import java.io.*;
import java.math.BigInteger;
import java.util.*;

public class Template implements Runnable {

    BufferedReader in;
    PrintWriter out;
    StringTokenizer tok = new StringTokenizer("");

    void init() throws FileNotFoundException {
        try {
            in = new BufferedReader(new FileReader("input.txt"));
            out = new PrintWriter("output.txt");
        } catch (Exception e) {
            in = new BufferedReader(new InputStreamReader(System.in));
            out = new PrintWriter(System.out);
        }
    }

    class GraphBuilder {
        int n, m;
        int[] x, y;
        int index;
        int[] size;

        GraphBuilder(int n, int m) {
            this.n = n;
            this.m = m;
            x = new int[m];
            y = new int[m];
            size = new int[n];
        }

        void add(int u, int v) {
            x[index] = u;
            y[index] = v;
            size[u]++;
            size[v]++;
            index++;
        }

        int[][] build() {
            int[][] graph = new int[n][];
            for (int i = 0; i < n; i++) {
                graph[i] = new int[size[i]];
            }
            for (int i = index - 1; i >= 0; i--) {
                int u = x[i];
                int v = y[i];
                graph[u][--size[u]] = v;
                graph[v][--size[v]] = u;
            }
            return graph;
        }
    }

    String readString() throws IOException {
        while (!tok.hasMoreTokens()) {
            try {
                tok = new StringTokenizer(in.readLine());
            } catch (Exception e) {
                return null;
            }
        }
        return tok.nextToken();
    }

    int readInt() throws IOException {
        return Integer.parseInt(readString());
    }

    int[] readIntArray(int size) throws IOException {
        int[] res = new int[size];
        for (int i = 0; i < size; i++) {
            res[i] = readInt();
        }
        return res;
    }

    long[] readLongArray(int size) throws IOException {
        long[] res = new long[size];
        for (int i = 0; i < size; i++) {
            res[i] = readLong();
        }
        return res;
    }

    long readLong() throws IOException {
        return Long.parseLong(readString());
    }

    double readDouble() throws IOException {
        return Double.parseDouble(readString());
    }

    <T> List<T>[] createGraphList(int size) {
        List<T>[] list = new List[size];
        for (int i = 0; i < size; i++) {
            list[i] = new ArrayList<>();
        }
        return list;
    }

    public static void main(String[] args) {
        new Template().run();
        // new Thread(null, new Template(), "", 1l * 200 * 1024 * 1024).start();
    }

    long timeBegin, timeEnd;

    void time() {
        timeEnd = System.currentTimeMillis();
        System.err.println("Time = " + (timeEnd - timeBegin));
    }

    long memoryTotal, memoryFree;

    void memory() {
        memoryFree = Runtime.getRuntime().freeMemory();
        System.err.println("Memory = " + ((memoryTotal - memoryFree) >> 10)
                + " KB");
    }

    public void run() {
        try {
            timeBegin = System.currentTimeMillis();
            memoryTotal = Runtime.getRuntime().freeMemory();
            init();
            solve();
            out.close();
            if (System.getProperty("ONLINE_JUDGE") == null) {
                time();
                memory();
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

    void solve() throws IOException {
        int n = readInt();
        int m = readInt();
        int max = 1 << n;
        long[][] dp = new long[n][max];
        for (int i = 0; i < n; i++) {
            dp[i][1 << i] = 1;
        }
        GraphBuilder gb = new GraphBuilder(n, m);
        for (int i = 0; i < m; i++) {
            gb.add(readInt() - 1, readInt() - 1);
        }
        int[][] graph = gb.build();
        for (int mask = 1; mask < max; mask++) {
            int firstBit = -1;
            for (int i = 0; i < n; i++) {
                if (hasBit(mask, i)) {
                    firstBit = i;
                    break;
                }
            }
            for (int last = 0; last < n; last++) {
                if (dp[last][mask] == 0) continue;
                for (int y : graph[last]) {
                    if (!hasBit(mask, y) && y > firstBit) {
                        dp[y][mask | (1 << y)] += dp[last][mask];
                    }
                }
            }
        }
        long answer = 0;
        for (int i = 1; i < max; i++) {
            if (Integer.bitCount(i) < 3) continue;
            int firstBit = -1;
            for (int j = 0; j < n; j++) {
                if (hasBit(i, j)) {
                    firstBit = j;
                    break;
                }
            }
            for (int y : graph[firstBit]) {
                answer += dp[y][i];
            }
        }
        out.println(answer / 2);
    }

    boolean hasBit(int mask, int bit) {
        return (mask & (1 << bit)) != 0;
    }

}


import java.io.*;
import java.util.StringTokenizer;

public class Main {

	private FastScanner in;
	private PrintWriter out;

	public void solve() throws IOException {
		int N = in.nextInt();
		int M = in.nextInt();

		int[][] edges = new int[N][N];
		for (int i = 0; i < M; i++) {
			int a = in.nextInt() - 1;
			int b = in.nextInt() - 1;
			edges[a][b] = 1;
			edges[b][a] = 1;
		}

		int globalCountMasks = 1 << N;
		int[][] masks = new int[N + 1][];
		int[] countMasks = new int[N + 1];
		for (int i = 0; i <= N; i++) {
			masks[i] = new int[combinations(N, i)];
		}
		for (int i = 0; i < globalCountMasks; i++) {
			int c = countBit1(i);
			masks[c][countMasks[c]] = i;
			countMasks[c]++;
		}

		long globalCountCycles = 0;
		long[][] count = new long[globalCountMasks][N];

		for (int a = 0; a < N - 2; a++) {
			int aBit = 1 << a;
			count[aBit][a] = 1;
			long countCycles = 0;
			for (int i = 2; i <= N; i++) {
				for (int m = 0; m < countMasks[i]; m++) {
					int mask = masks[i][m];
					if ((mask & aBit) == 0)
						continue;
					if ((mask & (aBit - 1)) > 0)
						continue;
					count[mask][a] = 0;
					for (int v = a + 1; v < N; v++) {
						int vBit = 1 << v;
						if ((mask & vBit) == 0)
							continue;
						count[mask][v] = 0;
						for (int t = a; t < N; t++) {
							if ((mask & (1 << t)) == 0 || t == v
									|| edges[v][t] == 0)
								continue;
							count[mask][v] += count[mask ^ vBit][t];
						}
						if (edges[a][v] == 1 && mask != (aBit | vBit)) {
							countCycles += count[mask][v];
						}
					}
				}
			}
			globalCountCycles += countCycles / 2;
		}

		out.println(globalCountCycles);
	}

	private int countBit1(int k) {
		int c = 0;
		while (k > 0) {
			c += k & 1;
			k >>= 1;
		}
		return c;
	}

	private int combinations(int n, int k) {
		if (k > n || k < 0) {
			throw new IllegalArgumentException();
		}
		int r = 1;
		for (int i = 1; i <= k; i++) {
			r = r * (n + 1 - i) / i;
		}
		return r;
	}

	public void run() {
		try {
			in = new FastScanner(System.in);
			out = new PrintWriter(System.out);
			solve();
			out.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	class FastScanner {

		BufferedReader br;
		StringTokenizer st;

		FastScanner(InputStream is) {
			br = new BufferedReader(new InputStreamReader(is));
		}

		String next() {
			while (st == null || !st.hasMoreTokens()) {
				try {
					st = new StringTokenizer(br.readLine());
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			return st.nextToken();
		}

		int nextInt() {
			return Integer.parseInt(next());
		}
	}

	public static void main(String[] arg) {
		new Main().run();
	}
}


import java.io.*;
import java.util.*;

public class Template implements Runnable {
    private void solve() throws IOException {
        int n = nextInt();
        int m = nextInt();
        boolean[][] g = new boolean[n][n];
        for (int i = 0; i < m; ++i) {
            int a = nextInt() - 1;
            int b = nextInt() - 1;
            g[a][b] = true;
            g[b][a] = true;
        }
        /*for (int i = 0; i < n; ++i)
            for (int j = 0; j < n; ++j)
                g[i][j] = true;*/
        long[] am = new long[n + 1];
        long[][] ways = new long[1 << (n - 1)][n];
        for (int start = 0; start < n; ++start) {
            for (int mask = 0; mask < (1 << (n - start - 1)); ++mask)
                for (int last = start; last < n; ++last) {
                    ways[mask][last - start] = 0;
                }
            ways[1 >> 1][0] = 1;
            for (int mask = 1; mask < (1 << (n - start)); mask += 2) {
                int cnt = 0;
                int tmp = mask;
                while (tmp > 0) {
                    ++cnt;
                    tmp = tmp & (tmp - 1);
                }
                for (int last = start; last < n; ++last)
                    if (ways[mask >> 1][last - start] > 0) {
                        long amm = ways[mask >> 1][last - start];
                        for (int i = start; i < n; ++i)
                            if ((mask & (1 << (i - start))) == 0 && g[last][i]) {
                                ways[(mask | (1 << (i - start))) >> 1][i - start] += amm;
                            }
                        if (g[last][start])
                            am[cnt] += ways[mask >> 1][last - start];
                    }
            }
        }
        long res = 0;
        for (int cnt = 3; cnt <= n; ++cnt) {
            if (am[cnt] % (2) != 0)
                throw new RuntimeException();
            res += am[cnt] / (2);
        }
        writer.println(res);
    }

    public static void main(String[] args) {
        new Template().run();
    }

    BufferedReader reader;
    StringTokenizer tokenizer;
    PrintWriter writer;

    public void run() {
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            tokenizer = null;
            writer = new PrintWriter(System.out);
            solve();
            reader.close();
            writer.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    int nextInt() throws IOException {
        return Integer.parseInt(nextToken());
    }

    long nextLong() throws IOException {
        return Long.parseLong(nextToken());
    }

    double nextDouble() throws IOException {
        return Double.parseDouble(nextToken());
    }

    String nextToken() throws IOException {
        while (tokenizer == null || !tokenizer.hasMoreTokens()) {
            tokenizer = new StringTokenizer(reader.readLine());
        }
        return tokenizer.nextToken();
    }
}


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.StringTokenizer;

/**
 * 4 6
 * 1 2
 * 1 3
 * 1 4
 * 2 3
 * 2 4
 * 3 4
 *
 *
 *
 *
 *
 *
 * @author pttrung
 */
public class D {

    public static long [][]dp;
    public static boolean [][]map;

    public static void main(String[] args) {
        Scanner in = new Scanner();
        PrintWriter out = new PrintWriter(System.out);
//        int N = 19;
//        System.out.println(N + " " + ((N - 1) * N / 2));
//        for (int i = 1; i
//                <= N; i++) {
//            for (int j = i + 1; j <= N; j++) {
//                System.out.println(i + " " + j);
//            }
//        }
        int n = in.nextInt();
        int m = in.nextInt();
        dp = new long[n][1 << n + 1] ;

        map = new boolean[n][n];
        for (int i = 0; i < m; i++) {
            int a = in.nextInt() - 1;
            int b = in.nextInt() - 1;
            map[a][b] = true;
            map[b][a] = true;

        }
        for(long []temp : dp){
            Arrays.fill(temp, -1);
        }
        long result = 0;
        for(int i = 0; i < n; i++){
            result += cal((1<<i),i,i,1);
        }
        out.println((result/2));

        out.close();
    }
    public static long cal(int mask, int from, int to, int len){
        if(dp[to][mask]!= -1){
            return dp[to][mask];
        }
        long result = 0;
        if(len > 2 && map[from][to]){
            result++;
        }
        for(int i = from+1; i< map.length; i++){
            if(map[to][i] && (mask & (1<<i)) == 0){
                result += cal(mask|(1<<i), from,i,len + 1);
            }
        }
        dp[to][mask] = result;
        return result;

    }

    public static class FT {

        int[] data;

        FT(int n) {
            data = new int[n];
        }

        public void update(int index, int value) {
            while (index < data.length) {
                data[index] += value;
                index += (index & (-index));
            }
        }

        public int get(int index) {
            int result = 0;
            while (index > 0) {
                result += data[index];
                index -= (index & (-index));
            }
            return result;

        }
    }

    public static long gcd(long a, long b) {
        if (b == 0) {
            return a;
        }
        return gcd(b, a % b);
    }

    public static long pow(long a, long b) {
        if (b == 0) {
            return 1;
        }
        if (b == 1) {
            return a;
        }
        long val = pow(a, b / 2);
        if (b % 2 == 0) {
            return val * val ;
        } else {
            return val * (val * a) ;
        }
    }

    static class Scanner {

        BufferedReader br;
        StringTokenizer st;

        public Scanner() {
            // System.setOut(new PrintStream(new BufferedOutputStream(System.out), true));
            br = new BufferedReader(new InputStreamReader(System.in));
        }

        public String next() {

            while (st == null || !st.hasMoreTokens()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (Exception e) {
                    throw new RuntimeException();
                }
            }
            return st.nextToken();
        }

        public long nextLong() {
            return Long.parseLong(next());
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public double nextDouble() {
            return Double.parseDouble(next());
        }

        public String nextLine() {
            st = null;
            try {
                return br.readLine();
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }

        public boolean endLine() {
            try {
                String next = br.readLine();
                while (next != null && next.trim().isEmpty()) {
                    next = br.readLine();
                }
                if (next == null) {
                    return true;
                }
                st = new StringTokenizer(next);
                return st.hasMoreTokens();
            } catch (Exception e) {
                throw new RuntimeException();
            }
        }
    }
}


import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class Main implements Runnable {

    private int n;
    private int nn;
    private long[][] dp;
    private boolean[][] gr;

    // ////////////////////////////////////////////////////////////////////
    // Solution

    private void solve() throws Throwable {
        n = nextInt();
        nn = 1 << n;
        gr = new boolean[n][n];
        dp = new long[n][nn];
        for (int i = 0; i < n; i++) {
            Arrays.fill(dp[i], -1);
        }
        int m = nextInt();
        for (int i = 0; i < m; i++) {
            int a = nextInt() - 1, b = nextInt() - 1;
            gr[a][b] = gr[b][a] = true;
        }
        for (int i = 0; i < n; i++) {
            dp[i][0] = 0;
        }
        for (int mask = 1; mask < nn; mask++) {
            int bCount = Integer.bitCount(mask);
            if (bCount < 2) {
                dp[Integer.numberOfTrailingZeros(mask)][mask] = 0;
            } else if (bCount == 2) {
                int msk = mask;
                int one = Integer.numberOfTrailingZeros(msk);
                msk ^= (1 << one);
                int two = Integer.numberOfTrailingZeros(msk);
                dp[two][mask] = gr[one][two] ? 1 : 0;
            }
        }
        long count = 0;
        for (int mask = 1; mask < nn; mask++) {
            if (Integer.bitCount(mask) < 3) continue;
            int i = Integer.numberOfTrailingZeros(mask);
            for (int j = i + 1; j < n; j++) {
                int jj = 1 << j;
                if (gr[i][j] && ((jj & mask) != 0)) {
                    count += Dp(j, mask);
                }
            }
        }
        pw.println(count / 2);
    }

    private long Dp(int j, int mask) {
        if (dp[j][mask] != -1) return dp[j][mask];
        int i = Integer.numberOfTrailingZeros(mask);
        assert i < j;
        int m = mask ^ (1 << j);
        long ans = 0;
        for (int p = i + 1; p < n; p++) {
            if (!gr[p][j]) continue;
            if ((mask & (1 << p)) == 0) continue;
            ans += Dp(p, m);
        }
        dp[j][mask] = ans;
        return ans;
    }

    // ////////////////////////////////////////////////////////////////////
    // Utility functions

    PrintWriter pw;
    BufferedReader in;
    StringTokenizer st;

    void initStreams() throws FileNotFoundException {
        //System.setIn(new FileInputStream("2"));
        in = new BufferedReader(new InputStreamReader(System.in));
        pw = new PrintWriter(System.out);
    }

    String nextString() throws IOException {
        while (st == null || !st.hasMoreTokens()) {
            st = new StringTokenizer(in.readLine());
        }
        return st.nextToken();
    }

    int nextInt() throws NumberFormatException, IOException {
        return Integer.parseInt(nextString());
    }

    long nextLong() throws NumberFormatException, IOException {
        return Long.parseLong(nextString());
    }

    double nextDouble() throws NumberFormatException, IOException {
        return Double.parseDouble(nextString());
    }

    static Throwable sError;

    public static void main(String[] args) throws Throwable {
        Thread t = new Thread(new Main());
        t.start();
        t.join();
        if (sError != null) {
            throw sError;
        }
    }

    public void run() {
        try {
            initStreams();
            solve();
        } catch (Throwable e) {
            sError = e;
        } finally {
            if (pw != null)
                pw.close();
        }
    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class Main {

	private int n;
	private int m;
	private boolean[][] g;
	private long[][] dp;
	private int[] count;
	private int[] first;

	private void solve() throws IOException {
		n = nextInt();
		m = nextInt();

		g = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			int a = nextInt() - 1;
			int b = nextInt() - 1;
			g[a][b] = true;
			g[b][a] = true;
		}

		count = new int[1 << n];
		first = new int[1 << n];
		dp = new long[1 << n][n];
		for (int mask = 0; mask < (1 << n); mask++) {
			count[mask] = bitCount(mask);
			for (int i = 0; i < n; i++) {
				if (bit(i, mask) == 1) {
					first[mask] = i;
					break;
				}
			}
		}

		for (int mask = 0; mask < (1 << n); mask++) {
			for (int i = 0; i < n; i++) {
				if ((count[mask] == 1) && (bit(i, mask) == 1)) {
					dp[mask][i] = 1;
				} else {
					for (int j = 0; j < n; j++) {
						if (g[j][i] && (count[mask] > 1) && (bit(i, mask) == 1)
								&& (first[mask] != i)) {
							dp[mask][i] += dp[mask ^ (1 << i)][j];
						}
					}
				}
			}
		}

		long ans = 0;
		for (int i = 0; i < n; i++) {
			for (int mask = 0; mask < (1 << n); mask++) {
				if ((count[mask] >= 3) && (first[mask] != i)
						&& (g[i][first[mask]])) {
					ans += dp[mask][i];
				}
			}
		}

		if (ans % 2 != 0) {
			throw new RuntimeException("SHIT!!!");
		}
		out.println(ans / 2);
	}

	private final int bit(int i, int mask) {
		return (mask & (1 << i)) != 0 ? 1 : 0;
	}

	private final int bitCount(int mask) {
		int ret = 0;
		while (mask != 0) {
			ret++;
			mask -= mask & (-mask);
		}
		return ret;
	}

	private BufferedReader in;
	private PrintWriter out;
	private StringTokenizer st;

	private Main() throws IOException {
		in = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);
		eat("");
		solve();
		in.close();
		out.close();
	}

	private void eat(String s) {
		st = new StringTokenizer(s);
	}

	String next() throws IOException {
		while (!st.hasMoreTokens()) {
			String line = in.readLine();
			if (line == null) {
				return null;
			}
			eat(line);
		}
		return st.nextToken();
	}

	int nextInt() throws IOException {
		return Integer.parseInt(next());
	}

	public static void main(String[] args) throws IOException {
		new Main();
	}

}


import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int V = in.nextInt();
        int E = in.nextInt();
        boolean [][] G = new boolean [V][V];
        for (int i = 0; i < E; i++) {
            int u = in.nextInt()-1;
            int v = in.nextInt()-1;
            G[u][v] = true;
            G[v][u] = true;
        }

        int pset = 1 << V;

        long [][] dp = new long [pset][V];
        long cycles = 0;

        for (int set = 1; set < pset; set++) {
            int bit = Integer.bitCount(set);
            int src = first(set);

            if (bit == 1) {
                dp[set][src] = 1;
            }
            else if(bit > 1) {
                for (int i = 0; i < V; i++) {
                    if(i == src) continue;
                    // Check if i is in set
                    if ((set & (1 << i)) != 0) {
                        int S_1 = set ^ (1 << i);
                        for (int v = 0; v < V; v++) {
                            if (G[v][i] == true) {
                                dp[set][i] += dp[S_1][v];
                            }
                        }
                    }
                    //Count Cycles:
                    if (bit >= 3 && G[src][i]) {
                        cycles += dp[set][i];
                    }
                }

            }

        }
        System.out.println(cycles/2);
    }

    public static int first(int n) {
        int cnt = 0;
        while ((n & 1) != 1) {
            cnt++;
            n >>= 1;
        }
        return cnt;
    }
}


import java.util.*;
import java.io.*;

public class Main implements Runnable {
	private void solution() throws IOException {
		int n = in.nextInt();
		int m = in.nextInt();
		boolean[][] adj = new boolean[n][n];
		long res = 0;
		for (int i = 0; i < m; ++i) {
			int x = in.nextInt();
			int y = in.nextInt();
			adj[x - 1][y - 1] = true;
			adj[y - 1][x - 1] = true;
		}
		for (int i = 0; i < n; ++i) {
			for (int j = i + 1; j < n; ++j) {
				if (adj[i][j]) {
					--res;
				}
			}
		}
		final long[][] dp = new long[1 << n][n];
		for (int i = 0; i < n; ++i) {
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					dp[mask][j] = 0;
				}
			}
			dp[0][0] = 1;
			for (int mask = 0; mask < (1 << (n - i)); ++mask) {
				for (int j = 0; j < n - i; ++j) {
					if (dp[mask][j] != 0) {
						for (int k = 0; k < n - i; ++k) {
							if (((mask >> k) & 1) == 0 && adj[j + i][k + i]) {
								dp[mask | (1 << k)][k] += dp[mask][j];
							}
						}
					}
				}
				if (((mask >> 0) & 1) != 0) {
					res += dp[mask][0];
				}
			}
		}
		out.println(res / 2);
	}

	public void run() {
		try {
			solution();
			in.reader.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	private class Scanner {
		BufferedReader reader;
		StringTokenizer tokenizer;

		public Scanner(Reader reader) {
			this.reader = new BufferedReader(reader);
			this.tokenizer = new StringTokenizer("");
		}

		public boolean hasNext() throws IOException {
			while (!tokenizer.hasMoreTokens()) {
				String next = reader.readLine();
				if (next == null) {
					return false;
				}
				tokenizer = new StringTokenizer(next);
			}
			return true;
		}

		public String next() throws IOException {
			hasNext();
			return tokenizer.nextToken();
		}

		public int nextInt() throws IOException {
			return Integer.parseInt(next());
		}

		public String nextLine() throws IOException {
			tokenizer = new StringTokenizer("");
			return reader.readLine();
		}

		public double nextDouble() throws IOException {
			return Double.parseDouble(next());
		}
	}

	public static void main(String[] args) throws IOException {
		new Thread(null, new Main(), "", 1 << 28).start();
	}
	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
	Scanner in = new Scanner(new InputStreamReader(System.in));
}


import java.io.*;
import java.util.*;
import static java.lang.Math.*;
import static java.util.Arrays.fill;
import static java.util.Arrays.binarySearch;
import static java.util.Arrays.sort;

public class Main {
	public static void main(String[] args) throws IOException {
//		try {
//			if (new File("input.txt").exists())
//				System.setIn(new FileInputStream("input.txt"));
//		} catch (SecurityException e) {}

//		long time1 = System.currentTimeMillis();
		new Main().run();
//		checkMemory();
//		long time2 = System.currentTimeMillis();
//		System.err.println((time2 - time1) + " ms");
	}

	BufferedReader in;
	PrintWriter out;
	StringTokenizer st = new StringTokenizer("");

	int vNum;
	int eNum;
	boolean[][] g;

	void run() throws IOException {
		in = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);

		vNum = nextInt();
		eNum = nextInt();
		g = new boolean [vNum][vNum];
		for (int e = 0; e < eNum; e++) {
			int u = nextInt() - 1;
			int v = nextInt() - 1;
			g[u][v] = g[v][u] = true;
		}

//		for (int v = 7; v <= 11; v++) {
//			genFullGraph(v);
//			if (naiveDP() != optimizedDP())
//				System.err.println("Error on " + v);
//		}

		out.println(optimizedDP());

		out.close();
	}

	/***************************************************************
	 * Test
	 **************************************************************/
	long naiveDP() {
		long[] count = new long [vNum + 1];
		int size = 1 << vNum;
		long[][] dp = new long [size][vNum];
		for (int begin = 0; begin < vNum; begin++) {
			for (long[] row : dp) fill(row, 0L);
			dp[1 << begin][begin] = 1L;
			for (int mask = 0; mask < size; mask++) {
				int len = Integer.bitCount(mask);
				for (int v = 0; v < vNum; v++) {
					long cval = dp[mask][v];
					if (cval == 0L) continue;
					if (g[v][begin]) count[len] += cval;
					for (int nv = 0; nv < vNum; nv++) {
						if (g[v][nv]) {
							int nmask = mask | (1 << nv);
							if (nmask != mask)
								dp[nmask][nv] += cval;
						}
					}
				}
			}
		}
		long ret = 0L;
		for (int len = 3; len <= vNum; len++) {
			if (count[len] % (len * 2) != 0) System.err.println("ERROR!");
			ret += count[len] / len / 2;
		}
		return ret;
	}

	long optimizedDP() {
		long[] count = new long [vNum + 1];
		long[][] dp = new long [1 << vNum][vNum];
		for (int last = vNum - 1; last >= 0; last--) {
			int size = 1 << last;
			for (int mask = 0; mask < size; mask++)
				fill(dp[mask], 0, last, 0L);
			for (int nv = 0; nv < last; nv++)
				if (g[last][nv]) dp[1 << nv][nv] = 1L;
			for (int mask = 0; mask < size; mask++) {
				int len = Integer.bitCount(mask) + 1;
				for (int v = 0; v < last; v++) {
					long cval = dp[mask][v];
					if (cval == 0L) continue;
					if (g[v][last]) count[len] += cval;
					for (int nv = 0; nv < last; nv++) {
						if (g[v][nv]) {
							int nmask = mask | (1 << nv);
							if (nmask != mask)
								dp[nmask][nv] += cval;
						}
					}
				}
			}
		}
		long ret = 0L;
		for (int len = 3; len <= vNum; len++) {
			if (count[len] % 2 != 0) System.err.println("ERROR!");
			ret += count[len] >> 1;
		}
		return ret;
	}

	void genFullGraph(int vNum) {
		this.vNum = vNum;
		this.eNum = vNum * (vNum - 1) / 2;
		g = new boolean [vNum][vNum];
		for (int i = 0; i < vNum; i++)
			for (int j = i + 1; j < vNum; j++)
				g[i][j] = g[j][i] = true;

	}

	/***************************************************************
	 * Utility
	 **************************************************************/
	static long b2mb(long b) {
		return b >> 20;
	}

	static void checkMemory() {
		System.err.println(b2mb(Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) + "/" + b2mb(Runtime.getRuntime().totalMemory()) + " MB");
	}

	/***************************************************************
	 * Input
	 **************************************************************/
	String nextToken() throws IOException {
		while (!st.hasMoreTokens())
			st = new StringTokenizer(in.readLine());
		return st.nextToken();
	}

	int nextInt() throws IOException {
		return Integer.parseInt(nextToken());
	}

	long nextLong() throws IOException {
		return Long.parseLong(nextToken());
	}

	double nextDouble() throws IOException {
		return Double.parseDouble(nextToken());
	}

	String nextLine() throws IOException {
		st = new StringTokenizer("");
		return in.readLine();
	}

	boolean EOF() throws IOException {
		while (!st.hasMoreTokens()) {
			String s = in.readLine();
			if (s == null)
				return true;
			st = new StringTokenizer(s);
		}
		return false;
	}
}