import java.io.IOException;
import java.io.InputStream;

public class Cycles
{
	public static FastInputStream fis;

	public static void main(String[] args) throws IOException
	{
		fis = new FastInputStream(System.in);

		System.out.println(solve(fis.nextInt(), fis.nextInt()));

		fis.close();
	}

	public static long solve(int n, int m) throws IOException
	{
		boolean[][] graph = new boolean[n][];
		long[][] state = new long[1 << n][n];

		for (int i = 0; i < n; i++)
			graph[i] = new boolean[n - i];

		for (int i = 0; i < m; i++)
		{
			int a = fis.nextInt() - 1;
			int b = fis.nextInt() - 1;
			setConnected(graph, a, b);
			state[(1 << a) | (1 << b)][a > b ? a : b] = 1;
		}

		long res = 0;

		for (int i = 2; i < n; i++)
		{
			int baseCombination = (2 << i) - 1;

			while (baseCombination < (1 << n))
			{
				int min = getFirstOne(baseCombination);
				int bits = baseCombination;
				while (bits != 0)
				{
					int firstBit = bits & (-bits);
					int firstBitPos = getFirstOne(firstBit);

					bits &= bits - 1;

					if (firstBitPos == min)
						continue;

					int leftOverBits = baseCombination - firstBit;
					int nextBits = leftOverBits;

					while (nextBits != 0)
					{
						int nextBit = nextBits & (-nextBits);
						int nextBitPos = getFirstOne(nextBit);

						nextBits &= nextBits - 1;

						if (nextBitPos == min)
							continue;

						if (!isConnected(graph, firstBitPos, nextBitPos))
							continue;

						/*System.out.println("Comb: " + Integer.toBinaryString(baseCombination));
						System.out.println("Bit: " + Integer.toBinaryString(firstBit));
						System.out.println("(pos): " + firstBitPos);
						System.out.println("Left over: " + Integer.toBinaryString(leftOverBits));
						System.out.println("Next bit: " + Integer.toBinaryString(nextBit));
						System.out.println("(pos): " + nextBitPos);
						System.out.println();*/

						state[baseCombination][firstBitPos] += state[leftOverBits][nextBitPos];
					}

					if (isConnected(graph, firstBitPos, min))
						res += state[baseCombination][firstBitPos];
				}

				baseCombination = nextCombination(baseCombination);
			}
		}

		/*for (int i = 0; i < state.length; i++)
		{
			if ((i & (i - 1)) == 0)
				continue;
			System.out.print(String.format("%4s:", Integer.toBinaryString(i)).replace(' ', '0') + " ");
			for (int j = 0; j < state[i].length; j++)
				System.out.print(state[i][j] + " ");
			System.out.println();
		}*/

		return res >> 1;
	}

	public static boolean isConnected(boolean[][] graph, int a, int b)
	{
		if (b < a || graph[a].length <= (b - a))
			return graph[b][a - b];
		return graph[a][b - a];
	}

	public static void setConnected(boolean[][] graph, int a, int b)
	{
		if (b < a || graph[a].length <= (b - a))
			graph[b][a - b] = true;
		else
			graph[a][b - a] = true;
	}

	public static int nextCombination(int x)
	{
		int smallest = x & -x;
		int ripple = x + smallest;
		int ones = ((x ^ ripple) >> 2) / smallest;
		return ripple | ones;
	}

	public static boolean on(int bitmask, int pos)
	{
		return ((bitmask >> pos) & 1) == 1;
	}

	public static int setOn(int bitmask, int pos)
	{
		return bitmask | (1 << pos);
	}

	public static int setOff(int bitmask, int pos)
	{
		return bitmask & ~(1 << pos);
	}

	public static int getOns(int bitmask)
	{
		int amt = 0;
		while (bitmask != 0)
		{
			bitmask &= bitmask - 1;
			amt++;
		}

		return amt;
	}

	public static int getFirstOne(int bitmask)
	{
		if (bitmask == 0)
			return -1;

		int first = 0;
		while ((bitmask & 1) != 1)
		{
			first++;
			bitmask = bitmask >> 1;
		}

		return first;
	}

	public static class FastInputStream extends InputStream
	{
		private InputStream in;

		private byte[] buffer = new byte[512];
		private int loaded = 0;
		private int pointer = 0;

		public FastInputStream(InputStream in)
		{
			this.in = in;
		}

		@Override
		public int read() throws IOException
		{
			if (hasNext())
				return buffer[pointer++];
			else
				return -1;
		}

		public void skipWhitespace() throws IOException
		{
			while (hasNext())
			{
				char c = (char) buffer[pointer];

				if (c == ' ' || c == '\t' || c == '\n' || c == '\r') pointer++;
				else return;
			}
		}

		public Integer nextInt() throws IOException
		{
			skipWhitespace();

			if (!hasNext()) return null;

			byte multiplier = 1;
			int number = 0;

			if (buffer[pointer] == '-')
			{
				multiplier = -1;
				pointer++;
			}

			while (hasNext())
			{
				char c = (char) buffer[pointer];

				if (c >= '0' && c <= '9')
				{
					number = (number << 3) + (number << 1) + c - '0';
					pointer++;
				} else break;
			}

			return number * multiplier;
		}

		public void close() throws IOException
		{
			in.close();
		}

		public boolean hasNext() throws IOException
		{
			while (pointer == loaded)
			{
				loaded = in.read(buffer);
				pointer = 0;

				if (loaded == -1) return false;
			}
			return loaded != -1;
		}
	}
}


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

public class Ok_Simple {
	static BufferedReader reader;
	static StringTokenizer tokenizer;
	static boolean am[][];
	static long dp[][];
	static int n;
	public static void main(String[] args) throws IOException {
		reader = new BufferedReader(new InputStreamReader(System.in));
		int m;
		n = NextInt();
		m = NextInt();
		am = new boolean[n][n];
		dp = new long[n][1 << n];
		for (int i = 0; i < n; ++i)
			Arrays.fill(dp[i], -1);
		for (int i = 0; i < m; ++i) {
			int a = NextInt() - 1;
			int b = NextInt() - 1;
			am[a][b] = am[b][a] = true;
		};
		long res = 0;
		for (int a = 0; a < n; ++a)
			res += solve(a, (1 << a));
		System.out.println(res / 2);
	}
	private static long solve(int b, int mask) {
		int a = 0;
		for (int i = 0 ;i < n; ++i)
			if (((mask >> i) & 1) != 0)
			{
				a = i;
				break;
			}
		if (dp[b][mask] >= 0)
			return dp[b][mask];
		long res = 0;
		if (am[b][a] && Integer.bitCount(mask) > 2)
			res = 1;
		for (int i = a + 1; i < n; ++i)
			if (((mask >> i) & 1) == 0 && am[b][i])
				res += solve(i, mask ^ (1 << i));
		return dp[b][mask] = res;
	}
	static int NextInt() throws NumberFormatException, IOException {
		return Integer.parseInt(NextToken());
	}
	static double NextDouble() throws NumberFormatException, IOException {
		return Double.parseDouble(NextToken());
	}
	static long NextLong() throws NumberFormatException, IOException {
		return Long.parseLong(NextToken());
	}
	static String NextToken() throws IOException {
		while(tokenizer == null || !tokenizer.hasMoreTokens()) {
			tokenizer = new StringTokenizer(reader.readLine());
		}
		return tokenizer.nextToken();
	}
}


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

public class Main {

	public static void main(String[] args) throws java.lang.Exception {
		BufferedReader kek = new BufferedReader(new InputStreamReader(System.in));
		//Scanner skek = new Scanner(System.in);
		PrintWriter outkek = new PrintWriter(System.out);

		String[] input = kek.readLine().split(" ");
		int N = Integer.parseInt(input[0]), M = Integer.parseInt(input[1]);
		boolean[][] connected = new boolean[N + 1][N];
		long[][] walks = new long[1 << N][N];
		long res = 0;

		for(int i = 0; i < M; i++){
			input = kek.readLine().split(" ");
			int A = Integer.parseInt(input[0]) - 1, B = Integer.parseInt(input[1]) - 1;
			connected[A][B] = connected[B][A] = true;
		}

		for(int i = 0; i < N; i++){
			walks[1 << i][i] = 1;
		}

		for(int i = 1; i < 1 << N; i++){
			int temp = (int) (Math.log(i & -(i)) / 0.6931471805599453);
			for(int j = 0; j < N; j++){
				if(((1 << j) & i) > 0 && j != temp){
					for(int k = 0; k < N; k++){
						if(connected[k][j]){
							walks[i][j] += walks[i ^ (1 << j)][k];
						}
					}

					int count = 0, track = i;
					while(track > 0){
						if(track % 2 == 1){
							count++;
						}
						track /= 2;
					}

					if(count >= 3 && connected[temp][j]){
						res += walks[i][j];
					}
				}
			}
		}

		outkek.println(res / 2);
		kek.close();
		outkek.close();
	}

}


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

public class ASimpleTask {

	static StreamTokenizer st;

	static int nextInt() {
		try {
			st.nextToken();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return (int) st.nval;
	}

	static int[][] edges;
	static long[][] dp;

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

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

		edges = new int[n][n];

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

		dp = new long[(1 << n) + 1][n + 1];

		for (int mask = 1; mask < (1 << n); mask++) {
			for (int i = 0; i < n; i++) {
				if (Integer.bitCount(mask) == 1 && (mask & (1 << i)) != 0) {
					dp[mask][i] = 1;
					continue;
				}

				if (Integer.bitCount(mask) > 1 && (mask & (1 << i)) != 0
						&& first(mask, n) != i) {
					for (int j = 0; j < n; j++) {
						if (edges[i][j] == 1) {
							dp[mask][i] += dp[mask ^ (1 << i)][j];
						}
					}
				}
			}
		}

		long count = 0;
		for (int mask = 1; mask < (1 << n); mask++) {
			for (int i = 0; i < n; i++) {
				if (Integer.bitCount(mask) >= 3
						&& edges[i][first(mask, n)] != 0)
					count += dp[mask][i];
			}
		}

		System.out.println(count / 2);

	}

	static int first(int mask, int n) {
		for (int i = 0; i < n; i++) {
			if ((mask & (1 << i)) != 0)
				return i;
		}
		return -1;
	}

}


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 boolean[][] gr;
    private long[][] D;

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

    private void solve() throws Throwable {
        n = nextInt();
        nn = 1 << n;
        gr = new boolean[n][n];
        int m = nextInt();
        for (int i = 0; i < m; i++) {
            int a = nextInt() - 1, b = nextInt() - 1;
            gr[a][b] = gr[b][a] = true;
        }
        D = new long[n][nn];
        for (int i = 0; i < n; i++) {
            Arrays.fill(D[i], -1);
        }
        long count = 0;
        for (int i = 0; i < n; i++) {
            count += getD(i, i, 1, 1 << i);
        }
        pw.println(count / 2);
    }

    private long getD(int first, int last, int cnt, int mask) {
        if (D[last][mask] != -1) return D[last][mask];
        long ans = 0;
        if (cnt >= 3 && gr[first][last])
            ans++;
        for (int i = first + 1; i < n; i++) {
            if (gr[last][i] && (mask & (1 << i)) == 0) {
                ans += getD(first, i, cnt + 1, mask | (1 << i));
            }
        }
        D[last][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.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)) // according to our convention A simple path is not allowed to end at the lowest vertex
            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);
		solveBottomUp(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 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) {
				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;
							}
						}
					}
				}
				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.util.InputMismatchException;
import java.io.*;
import java.util.HashMap;

/**
 * Generated by Contest helper plug-in
 * Actual solution is at the bottom
 */
public class Main {
    public static void main(String[] args) {
        InputReader in = new StreamInputReader(System.in);
        PrintWriter out = new PrintWriter(System.out);
        run(in, out);
    }

    public static void run(InputReader in, PrintWriter out) {
        Solver solver = new SimpleCycles();
        solver.solve(1, in, out);
        Exit.exit(in, out);
    }
}

class StreamInputReader extends InputReader {
    private InputStream stream;
    private byte[] buf = new byte[1024];
    private int curChar, numChars;

    public StreamInputReader(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++];
    }

    @Override
    public void close() {
        try {
            stream.close();
        } catch (IOException ignored) {
        }
    }
}

abstract class InputReader {
    private boolean finished = false;

    public abstract int read();

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

    public String nextToken() {
        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;
    }

    public void setFinished(boolean finished) {
        this.finished = finished;
    }

    public abstract void close();
}

interface Solver {
    public void solve(int testNumber, InputReader in, PrintWriter out);
}

class Exit {
    private Exit() {
    }

    public static void exit(InputReader in, PrintWriter out) {
        in.setFinished(true);
        in.close();
        out.close();
    }
}

class SimpleCycles implements Solver {
    public void solve(int testNumber, InputReader in, PrintWriter out) {
        int n = in.nextInt();
        int m = in.nextInt();
        boolean[][] g = new boolean[n][n];
        for (int i = 0; i < m; ++i) {
            int u = in.nextInt();
            int v = in.nextInt();
            --u;
            --v;
            g[u][v] = g[v][u] = true;
        }
        HashMap<Integer, Integer> pointer = new HashMap<Integer, Integer> () ;
        for (int i =0 ; i < n; ++i) {
            pointer.put(1 << i, i);
        }
        long[][] dm = new long[1 << n][n];
        for (int i = 0; i < n; ++i) {
            dm[1 << i][i] = 1;
        }
        for (int i = 0; i < (1 << n); ++i) {
            for (int j = 0; j < n; ++j) {
                if (dm[i][j] == 0) continue;
                int k = pointer.get(i - (i & (i - 1)));
                for (int u = k + 1; u < n; ++u) {
                    if (g[j][u] && (i & (1 << u)) == 0) {
                        dm[i | (1 << u)][u] += dm[i][j];
                    }
                }
            }
        }
        long res = 0;
        for (int i = 0; i < (1 << n); ++i) {
            for (int j = 0; j < n; ++j)
                if (Integer.bitCount(i) >= 3) {

                    int c = pointer.get(i - (i & (i - 1)));
                    if (g[c][j]) res += (long) dm[i][j];
                }
        }
        out.print(res / 2);
    }
}


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

public class SimpleTask {

    public static void main(String[] args) throws Exception {
        InputReader in = new InputReader(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        int[] g = new int[n];
        long[][] dp = new long[1 << n][n];
        for (int i = 0; i < m; i++) {
            int a = in.nextInt() - 1, b = in.nextInt() - 1;
            g[a] |= (1 << b);
            g[b] |= (1 << a);
        }
        int all = (1 << n) - 1;
        for (int i = 0; i < n; i++) {
            int l = (1 << i);
            int left = all ^ (l - 1) ^ l;
            for (int j = left; j > 0; j = (j - 1) & left)
                if ((j & (j - 1)) != 0) {
                    dp[j | l][i] = 1;
                }
        }
        for (int i = (1 << n) - 1; i > 0; i--) {
            int last = i & -i;
            for (int j = 0; j < n; j++) {
                if (((1 << j) == last && (i & (i - 1)) != 0)
                        || ((1 << j) & i) == 0)
                    continue;
                for (int k = 0; k < n; k++) {
                    if ((1 << k) >= last && ((1 << k) & g[j]) != 0
                            && ((1 << k) == last || ((1 << k) & i) == 0)) {
                        dp[i][j] += dp[i | (1 << k)][k];
                    }

                }
            }
        }
        long res = 0;
        for (int i = 0; i < n; i++)
            res += dp[(1 << i)][i];
        System.out.println(res / 2);
    }

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

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream));
            tokenizer = 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());
        }
    }
}


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 A{
	static int n,m,start;
	static boolean [][] adj;
	static long [][] mem;
	public static void main(String[] args)throws Throwable {
		Scanner sc=new Scanner(System.in);
		n=sc.nextInt();
		m=sc.nextInt();
		adj=new boolean [n][n];
		for(int i=0;i<m;i++){
			int u=sc.nextInt()-1;
			int v=sc.nextInt()-1;
			adj[u][v]=true;
			adj[v][u]=true;
		}
		mem=new long [n+1][(1<<n)];
		for(int i=0;i<=n;i++)
			Arrays.fill(mem[i], -1);
		long ans=0;
		for(int i=0;i<n;i++){
			start=i;
			ans+=dp(i, (1<<i));
		}
		System.out.println(ans/2);
	}

	public static long dp(int i,int msk){
		if(mem[i][msk]!=-1)
			return mem[i][msk];
		long ans=0;
		if(adj[i][start] && Integer.bitCount(msk)>2)
			ans++;
		for(int j=start+1;j<n;j++){
			if(adj[i][j] && (msk & (1<<j))==0){
				ans+=dp(j, msk | (1<<j));
			}
		}
		return mem[i][msk]=ans;
	}

	static class Scanner {
		StringTokenizer st;
		BufferedReader br;
		public Scanner(InputStream s){	br = new BufferedReader(new InputStreamReader(s));}
		public String next() throws IOException {
			while (st == null || !st.hasMoreTokens()) st = new StringTokenizer(br.readLine());
			return st.nextToken();}
		public int nextInt() throws IOException {return Integer.parseInt(next());}
		public long nextLong() throws IOException {return Long.parseLong(next());}
		public String nextLine() throws IOException {return br.readLine();}
		public double nextDouble() throws IOException { return Double.parseDouble(next()); }
		public boolean ready() throws IOException {return br.ready();}
	}
}


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

/**
 * Created by huhansan on 2017/12/19.
 * http://codeforces.com/problemset/problem/11/D
 * 参考 Petr http://codeforces.com/contest/11/submission/47646
 * 求所有不同的环的个数
 * 首先按长度分 C = C1+C2+...+Cn
 * 长度为i的环的数量的求解， 规定一个顺序，以最小值开头，按开头不同分治，再按不同结尾分治
 * 遍历所有排列，如果长度为i的排列成环，则对应的Ci值+1
 * 遍历方式的选择：
 * 1.integer实现bitmask，从1加到1<<n
 * 2.递归
 */

public class CF_11D {
    long[][] ways;  //ways[mask][endPointIndex]
    boolean[][] connect;
    int n, m, lowestIndex, mask;    //n 节点数， m边数    00000...0  <--> n:n-1:n-2.....1 bitmask
    private static int HEAD_POINT_INDEX = 0;

    public void solve() throws Exception {
        int a, b;
        long total = 0;
        n = nextInt();
        m = nextInt();
        connect = new boolean[n][n];
        ways = new long[1 << n][n];
        for (int i = 0; i < m; i++) {
            a = nextInt();
            b = nextInt();
            connect[a - 1][b - 1] = true;
            connect[b - 1][a - 1] = true;
        }
        for (int i = 0; i < n; i++) {
            ways[1 << i][i] = 1;  //初始化，单定点也视为环
        }
        for (mask = 1; mask < 1 << n; mask++) {
            int tmp = mask, cnt = 0;
            while (tmp > 0) {
                tmp = tmp & (tmp - 1);
                cnt++;
            }
            lowestIndex = -1;
            for (int endPointIndex = 0; endPointIndex < n; endPointIndex++) {
                if ((mask & 1 << endPointIndex) != 0) {
                    if (lowestIndex < 0) {
                        lowestIndex = endPointIndex;
                    } else if (lowestIndex != endPointIndex) {
                        for (int i = lowestIndex; i < n; i++)
                            if (connect[i][endPointIndex]) {
                                ways[mask][endPointIndex] += ways[mask & ~(1 << endPointIndex)][i]; // P[(1,2,3)4]的数量 == 其它+P[1,2,3]的数量， 也就是1,2,3三个点可以组成的以1开头的排列数
                            }
                        if (connect[endPointIndex][lowestIndex] && cnt > 2) {  //以首尾定点分治，存在对称性，如 1-2-3-4-1 == 1-4-3-2-1
                            total += ways[mask][endPointIndex];
                        }
                    }
                }
            }
        }
        writer.println(total / 2);
    }

    public static void main(String[] args) {
        new CF_11D().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.awt.List;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;
import java.util.StringTokenizer;

public class T {
	static Scanner in = new Scanner();
	static PrintWriter out = new PrintWriter(System.out);
	static boolean adj[][];
	static int n, m, from;
	static long memo[][];
	static long Num_Cycle;

	public static void main(String[] args) throws IOException {

		n = in.nextInt();
		m = in.nextInt();
		adj = new boolean[n][n];
		memo = new long[n][1 << n];
		for (int i = 0; i < m; i++) {
			int u = in.nextInt() - 1;
			int v = in.nextInt() - 1;
			adj[u][v] = adj[v][u] = true;
		}
		for (long arr[] : memo) {
			Arrays.fill(arr, -1);
		}
		Num_Cycle = 0L;
		for (int i = 0; i < n; i++) {
			from = i;
			Num_Cycle += dp(from, (1 << i));
		}
		out.println(Num_Cycle / 2);

		out.flush();
		out.close();
	}

	static long dp(int start, int mask) {
		if (memo[start][mask] != -1) {
			return (memo[start][mask]);
		}
		long ans = 0L;
		if (adj[start][from] && Integer.bitCount(mask) >= 3) {// Cycle has
																// atleast 3
																// node and 3
																// edges
			ans++;
		}
		for (int i = from + 1; i < n; i++) {
			if (adj[start][i] && ((mask & (1 << i)) == 0)) {
				ans += dp(i, mask | (1 << i));
			}
		}
		return memo[start][mask] = ans;
	}

	static class Scanner {
		BufferedReader br;
		StringTokenizer st;

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

		Scanner(String file) throws FileNotFoundException {
			br = new BufferedReader(new FileReader(file));
		}

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

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

		String nextLine() throws IOException {
			return br.readLine();
		}

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

	}
}


import java.util.Arrays;
import java.util.Scanner;

public class C {
    static boolean[][] matrix;
    static long[][] dp;
    static int n;
    static int m;
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        n = s.nextInt();
        m = s.nextInt();
        matrix = new boolean[n][n];
        for (int i=0; i<m; ++i) {
            int v1 = s.nextInt()-1;
            int v2 = s.nextInt()-1;
            matrix[v1][v2] = true;
            matrix[v2][v1] = true;
        }
        dp = new long[n][1<<n+1];
        for (int i=0; i<n; ++i) Arrays.fill(dp[i], -1);

        long res = 0;
        for (int i=0; i<n; ++i)
            res += calc(i, i, (1<<i), 1);

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

    public static long calc(int h, int c, int m, int len) {
        if (dp[c][m] != -1)
            return dp[c][m];

        long ret = 0;
        if (len > 2 && matrix[c][h])
            ret = 1;
        for (int i=h+1; i<n; ++i)
            if ((m & (1<<i)) == 0 && matrix[c][i])
                ret += calc(h, i, m | (1<<i), len + 1);
        return dp[c][m] = ret;
    }
}


import java.io.IOException;
import java.io.PrintStream;
import java.util.*;

public class Template {

    public static void main(String[] args) throws IOException {
        final Scanner in = new Scanner(System.in);
        final PrintStream out = System.out;
        int n = in.nextInt(), m = in.nextInt();
        boolean[][] g = new boolean[n][n];
        for (int i = 0; i < m; ++i) {
            int a = in.nextInt(), b = in.nextInt();
            --a; --b;
            g[a][b] = g[b][a] = true;
        }
        final int mx = 1<<n;
        long[][] dp = new long[mx][n];
        long res = 0;
        for (int mask = 0; mask < mx; ++mask)
            for (int i = 0; i < n; ++i) {
                if (mask == (1 << i)) {
                    dp[mask][i] = 1;
                } else if (((mask & (1 << i)) != 0) && ((mask & ((1 << i) - 1)) != 0)) {
                    long r = 0;
                    int next = mask ^ (1<<i);
                    for (int j = 0; j < n; ++j) if (g[i][j]) {
                        r += dp[next][j];
                    }
                    dp[mask][i] = r;
                } else {
                    dp[mask][i] = 0;
                }

                if ((mask & (mask-1)) != 0 && g[i][lowestBit(mask)]) {
                    res += dp[mask][i];
                }

            }

        System.out.println((res-m)/2);
    }

    private static int lowestBit(int mask) {
        for (int i = 0;;++i) {
            if ((mask & (1<<i)) > 0) {
                return i;
            }
        }
    }

}


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

public class Main11D {

    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 Main11D().run();
    }
}


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

import javax.sound.midi.Synthesizer;

public class Main {

	static int V;
	static ArrayList<Integer> adjList [];

	static int first(int a){
	    int idx = 0;
	    while (a > 0 && (a & 1) == 0) {
	        idx++;
	        a>>=1;
	    }
	    return idx;
	}
	static long Number_Of_Simple_Cycles () { // Time Complexity O(2 ^ n  * n ^ 2)
		long dp [][] = new long[1 << V][V];

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

		for (int mask = 1 ; mask < 1 << V ; ++mask) {
				if (Integer.bitCount(mask) <= 1) continue;
				for (int current = 0 ; current < V ; ++current) {
					if (((1 << current) & mask) == 0) continue;

					for (int last : adjList[current])
						if (current != first(mask))
							{
								dp[mask][current] += dp[mask ^ (1 << current)][last];
							}
				}
		}

		long ans = 0 ;
		int allVisited = (1 << V) - 1;
		for (int mask = 1 ; mask < 1 << V ; ++mask) {
			if (Integer.bitCount(mask) < 3) continue;
			for (int u = 0 ; u < V ; ++u) {
				if (adjList[u].contains(first (mask)))
					ans += dp[mask][u];
			}
		}
		return ans >> 1;
	}
	public static void main(String[] args) throws Exception {
		Scanner sc = new Scanner(System.in);
		StringBuilder sb = new StringBuilder();
		PrintWriter out = new PrintWriter(System.out);
		V = sc.nextInt();
		adjList = new ArrayList[V];
		for (int i = 0 ; i < V ; ++i) adjList[i] = new ArrayList<>();
		int E = sc.nextInt();
		while (E -- > 0) {
			int v = sc.nextInt() - 1;
			int u = sc.nextInt() - 1;
			adjList[v].add(u);
			adjList[u].add(v);
		}
		out.print(Number_Of_Simple_Cycles());
		out.flush();
		out.close();
	}

	static class Scanner {
		StringTokenizer st;
		BufferedReader br;

		public Scanner(InputStream s) {
			br = new BufferedReader(new InputStreamReader(s));
		}

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

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

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

		public String nextLine() throws IOException {
			return br.readLine();
		}

		public double nextDouble() throws IOException {
			String x = next();
			StringBuilder sb = new StringBuilder("0");
			double res = 0, f = 1;
			boolean dec = false, neg = false;
			int start = 0;
			if (x.charAt(0) == '-') {
				neg = true;
				start++;
			}
			for (int i = start; i < x.length(); i++)
				if (x.charAt(i) == '.') {
					res = Long.parseLong(sb.toString());
					sb = new StringBuilder("0");
					dec = true;
				} else {
					sb.append(x.charAt(i));
					if (dec)
						f *= 10;
				}
			res += Long.parseLong(sb.toString()) / f;
			return res * (neg ? -1 : 1);
		}

		public boolean ready() throws IOException {
			return br.ready();
		}

	}
}


//package round11;

import java.io.BufferedOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.util.Arrays;
import java.util.Scanner;

public class D {
	private Scanner in;
	private PrintWriter out;

	private boolean[][] g;

	public void solve()
	{
		n = ni();
		int m = ni();
		g = new boolean[n][n];
		for(int i = 0;i < m;i++){
			int f = ni();
			int t = ni();
			g[f-1][t-1] = true;
			g[t-1][f-1] = true;
		}

		long ret = 0L;
		cache = new long[20 << 19];
		for(int i = 0;i < n;i++){
			start = i;
			ret += rec(1 << start, i, 0);
		}
		out.println(ret/2);
	}

	private long[] cache;
	private int n;
	private int start;

	private long rec(int passed, int cur, int depth)
	{
		int code = cur << 19 | passed;
		if(cache[code] != 0)return cache[code];
		long ret = 0L;

		if(g[cur][start] && depth >= 2)ret++;
		for(int i = start + 1;i < n;i++){
			if((passed & (1 << i)) == 0 && g[cur][i]){
				ret += rec(passed | (1 << i), i, depth + 1);
			}
		}

		cache[code] = ret;
		return ret;
	}

	public void run() throws Exception
	{
//		int m = 19;
//		StringBuilder sb = new StringBuilder();
//		sb.append(m + " ");
//		sb.append((m*(m-1)/2) + " ");
//		for(int i = 1;i <= m;i++){
//			for(int j = i + 1;j <= m;j++){
//				sb.append(i + " " + j + "\n");
//			}
//		}
//
//		in = new Scanner(new StringReader(sb.toString()));
//		in = new Scanner(new StringReader("4 6 1 2 1 3 1 4 2 3 2 4 3 4"));
		in = new Scanner(System.in);
		System.setOut(new PrintStream(new BufferedOutputStream(System.out)));
		out = new PrintWriter(System.out);

//		int n = in.nextInt();
		int n = 1;
		for(int i = 1;i <= n;i++){
			long t = System.currentTimeMillis();
			solve();
			out.flush();
			System.err.printf("%04d/%04d %7d%n", i, n, System.currentTimeMillis() - t);
		}
	}

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

	private int ni() { return Integer.parseInt(in.next()); }
	private static void tr(Object... o) { System.out.println(o.length == 1 ? o[0] : Arrays.toString(o)); }
	private void tra(int[] a) {System.out.println(Arrays.toString(a));}
	private void tra(int[][] a)
	{
		for(int[] e : a){
			System.out.println(Arrays.toString(e));
		}
	}

}


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

public class B {
    static int first(int n)
    {
	int res = 0;
	while(n > 0 && (n & 1) == 0){
	    n >>= 1;
	    res++;
	}
	return res;
    }
    public static void main(String[] args) throws Exception
    {
	Scanner bf = new Scanner(System.in);
	PrintWriter out = new PrintWriter(System.out);
	int n = bf.nextInt(), m = bf.nextInt();
	ArrayList<Integer> [] adjList = new ArrayList[n];
	for (int i = 0; i < adjList.length; i++)
	{
	    adjList[i] = new ArrayList<Integer>();
	}
	for (int i = 0; i < m; i++)
	{
	    int u = bf.nextInt()-1, v = bf.nextInt()-1;
	    adjList[u].add(v);
	    adjList[v].add(u);
	}
	long [][] memo = new long[(1<<n)][n];
	for (int i = 0; i < n; i++)
	{
	    memo[1<<i][i] = 1;
	}
	long ans = 0;
	for (int i = 1; i < 1<<n; i++)
	{
	    if(Integer.bitCount(i) == 1) continue;
	    for (int j = 0; j < n; j++)
	    {
		if((i & (1<<j)) == 0 || j == first(i)) continue;
		for(int v:adjList[j])
		    memo[i][j] += memo[i^(1<<j)][v];
	    }
	}
	for (int i = 1; i < 1<<n; i++)
	{
	    if(Integer.bitCount(i) < 3) continue;
	    int t = first(i);
	    for (int j = 0; j < n; j++)
	    {
		if(adjList[j].contains(t))
		    ans += memo[i][j];
	    }
	}
	out.println(ans/2);
	out.flush();
	out.close();
    }

    static class Scanner {
	StringTokenizer st;
	BufferedReader br;

	public Scanner(InputStream s)
	{
	    br = new BufferedReader(new InputStreamReader(s));
	}

	public Scanner(FileReader fileReader)
	{
	    br = new BufferedReader(fileReader);
	}

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

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

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

	public String nextLine() throws IOException
	{
	    return br.readLine();
	}

	public boolean ready() throws IOException
	{
	    return br.ready();
	}
    }
}


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

public class Main {

	static long[][] memo;
	static boolean[][] adjMat;
	static int N, endNode;
	static ArrayList<Integer>[] bits;

	static long dp(int idx, int msk)	//complexity = N * 2^(N + 1)
	{
		if(memo[idx][msk] != -1)
			return memo[idx][msk];
		long ret = adjMat[idx][endNode] ? 1 : 0;
		for(int i = 0, sz = bits[msk].size(); i < sz; ++i)
		{
			int j = bits[msk].get(i);
			if(j > endNode)
				break;
			if(adjMat[idx][j])
				ret += dp(j, msk | 1 << j);
		}
		return memo[idx][msk] = ret;
	}

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

		N = sc.nextInt();
		adjMat = new boolean[N][N];

		bits = new ArrayList[1 << N];
		for(int i = 0; i < 1 << N; ++i)
		{
			bits[i] = new ArrayList<>(1);
			for(int j = 0; j < N; ++j)
				if((i & 1 << j) == 0)
					bits[i].add(j);
		}
		int M = sc.nextInt();
		while(M-->0)
		{
			int u = sc.nextInt() - 1, v = sc.nextInt() - 1;
			adjMat[u][v] = adjMat[v][u] = true;
		}
		long ans = 0;
		for(int i = N; i > 1; --i)
		{
			memo = new long[i][1 << i];
			for(long[] x: memo)
				Arrays.fill(x, -1);
			ans += dp(endNode = i - 1, 1 << endNode);
		}
		for(int i = 0; i < N; ++i)
			for(int j = i + 1; j < N; ++j)
				if(adjMat[i][j])
					--ans;
		out.println(ans >> 1);
		out.flush();
		out.close();
	}

	static class Scanner
	{
		StringTokenizer st;
		BufferedReader br;

		public Scanner(InputStream s){	br = new BufferedReader(new InputStreamReader(s));}

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

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

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

		public String nextLine() throws IOException {return br.readLine();}

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

		public boolean ready() throws IOException {return br.ready();}
	}
}


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.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.io.InputStreamReader;
import java.io.IOException;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author ATailouloute
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        QuickScanner in = new QuickScanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskD solver = new TaskD();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskD {
        public void solve(int testNumber, QuickScanner in, PrintWriter out) {
            int n = in.nextInt();
            int m = in.nextInt();
            boolean[][] is = new boolean[n][n];
            for (int i = 0; i < n; i++) {
                is[i][i] = true;
            }
            long[][] dp = new long[1 << n | 1][n];
            for (int i = 0; i < m; i++) {
                int u = in.nextInt() - 1;
                int v = in.nextInt() - 1;
                is[u][v] = is[v][u] = true;
                dp[(1 << u) + (1 << v)][v] = 1;
                dp[(1 << u) + (1 << v)][u] = 1;
            }
            int k = 0;
            long res = 0;
            for (int mask = 1; mask < 1 << n; ++mask) {
                boolean f = true;
                for (int i = 0; i < n; i++) {
                    if ((mask & (1 << i)) != 0 && dp[mask][i] == 0) {
                        if (f) {
                            f = false;
                            k = i;
                        } else {
                            for (int j = k + 1; j < n; j++) {
                                if ((mask & (1 << j)) != 0 && is[i][j]) {
                                    dp[mask][i] += dp[mask - (1 << i)][j];
                                }
                            }
                        }
                        if (is[k][i]) res += dp[mask][i];
                    }
                }
            }
            out.println(res >> 1);
        }

    }

    static class QuickScanner {
        BufferedReader br;
        StringTokenizer st;
        InputStream is;

        public QuickScanner(InputStream stream) {
            is = stream;
            br = new BufferedReader(new InputStreamReader(stream), 32768);
        }

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

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

    }
}


import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Scanner;

public class Main implements Runnable {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new Thread(new Main()).start();
	}

	public void run() {
		Locale.setDefault(Locale.US);
		try {
			run1();
		} catch (IOException e) {
			throw new RuntimeException();
		}
	}

	int nextInt(StreamTokenizer st) throws IOException {
		st.nextToken();
		return (int) st.nval;
	}

	private List<Integer> kmp(String x, String a) {
		String s = a + "$" + x;
		int[] oppa = new int[s.length()];
		oppa[0] = 0;
		int tmp = 0;
		List<Integer> res = new ArrayList<Integer>();
		for (int i = 1; i < s.length(); i++) {
			while (tmp != 0 && s.charAt(tmp) != s.charAt(i)) {
				// System.out.println(i + " " + tmp);
				tmp = oppa[tmp - 1];
			}
			if (s.charAt(tmp) == s.charAt(i))
				tmp++;
			oppa[i] = tmp;
			if (tmp == a.length()) {
				res.add(i - a.length() - a.length());
			}
		}
		return res;
	}

	double nextDouble(StreamTokenizer st) throws IOException {
		st.nextToken();
		return st.nval;
	}

	String nextLine(StreamTokenizer st) throws IOException {
		st.nextToken();
		return st.sval;
	}

	public void run1() throws IOException {
		Scanner sc = new Scanner(new InputStreamReader(System.in));
		// Scanner sc = new Scanner(new FileReader("input.txt"));
		int n = sc.nextInt();
		int m = sc.nextInt();
		boolean[][] arr = new boolean[n][n];
		for (int i = 0; i < m; i++) {
			int a = sc.nextInt();
			int b = sc.nextInt();
			arr[a - 1][b - 1] = true;
			arr[b - 1][a - 1] = true;
		}
		long[][] res = new long[n][1 << n];
		for (int mask = 1; mask < (1 << n); mask++) {
			int min = -1;
			for (int i = 0; i < n; i++) {
				if ((mask & (1 << i)) != 0) {
					if (min == -1) {
						min = i;
						if (mask == (1 << min))
							res[min][mask] = 1;
					}
					for (int j = min + 1; j < n; j++)
						if ((mask & (1 << j)) == 0 && arr[i][j]) {
							res[j][mask | (1 << j)] += res[i][mask];
						}
				}
			}
		}
		long r = 0;
		for (int mask = 1; mask < (1 << n); mask++)
			if (Integer.bitCount(mask) != 2)
				for (int j = 0; j < n; j++) {
					int i = 0;
					while ((mask & (1 << i)) == 0)
						i++;
					if (arr[i][j])
						r += res[j][mask];
				}
		System.out.println(r / 2);
	}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Stack;
import java.util.StringTokenizer;
import java.util.TreeSet;

public class D {
    static boolean[][] adj;
    static int n;
    static int first;

    public static void main(String[] args) throws IOException {
        InputReader in = new InputReader();
        n = in.nextInt();
//       n = 19;?
        int m = in.nextInt();
        adj = new boolean[n][n];
//       for (int i = 0; i < n; i++) {
//       for (int j = 0; j < n; j++) {
//       adj[i][j] = true;
//       }
//       }
        dp = new long[1 << n][n];
        for (int i = 0; i < m; i++) {
            int f = in.nextInt() - 1;
            int t = in.nextInt() - 1;
            adj[f][t] = adj[t][f] = true;
        }

        boolean[] v = new boolean[1 << n];
        long res = 0;
        for (int f = 0; f < n; f++) {
            first = f;
            int cnt;
            for (int i = 0; i < 1 << n; i+=(1<<first))
                if ((i & (1 << first)) == 0)
                    for (int j = 0; j < n; j++)
                        dp[i][j] = -1;

            for (int i = 0; i < 1 << n; i+= (1<<first)) {
                cnt = Integer.bitCount(i);
                if ((i & (1 << first)) == 0 && !v[i | (1 << first)] && cnt > 1) {
                    v[i | (1 << first)] = true;
                    res += solve(i, first, cnt);
                }
            }
        }
        System.out.println(res / 2);
    }

    static long[][] dp;

    public static long solve(int msk, int lst, int cnt) {
        if (cnt == 0)
            return (adj[lst][first]) ? 1 : 0;
        if (dp[msk][lst] != -1)
            return dp[msk][lst];
        long res = 0;
        for (int i = 0; i < n; i++)
            if (adj[lst][i] && (msk & (1 << i)) > 0)
                res += solve(msk ^ (1 << i), i, cnt - 1);
        return dp[msk][lst] = res;
    }

    static class InputReader {
        BufferedReader in;
        StringTokenizer st;

        public InputReader() throws IOException {
            in = new BufferedReader(new InputStreamReader(System.in));
            st = new StringTokenizer(in.readLine());
        }

        public String next() throws IOException {
            while (!st.hasMoreElements())
                st = new StringTokenizer(in.readLine());
            return st.nextToken();
        }

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

        public long nextLong() throws NumberFormatException, 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 = 0 ; 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 && (i&(1<<k))!=0 ) {
							res += dp[i][j];
						}
						if ( (i&(1<<k))==0 ) {
							dp[i|(1<<k)][k] += dp[i][j];
						}
					}
				}
			}
			res -= m;
			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.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;

/**
 * Created by Tejas on 18-10-2018.
 */
public class Main {

    static HashSet<Integer> adjList[];
    public static void main(String[]args)throws IOException{
        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
        StringBuilder stringBuilder=new StringBuilder();

        String temp[]=bufferedReader.readLine().split(" ");
        int V=Integer.parseInt(temp[0]);
        int E=Integer.parseInt(temp[1]);
        adjList=new HashSet[V];
        for(int i=0;i<V;i++)
            adjList[i]=new HashSet<>();
        for(int i=0;i<E;i++){
            temp=bufferedReader.readLine().split(" ");
            int x=Integer.parseInt(temp[0])-1;
            int y=Integer.parseInt(temp[1])-1;
            adjList[y].add(x);
            adjList[x].add(y);

        }
        stringBuilder.append(solve(V)+"\n");
        System.out.println(stringBuilder);
    }

    private static long solve(int V) {

        long dp[][]=new long[(1<<V)][V];

        for(int i=0;i<V;i++)
            dp[(1<<i)][i]=1;
        for(int mask=1;mask<(1<<V);mask++){
            // HW starting at pos first and ending at j.
            int first = Integer.numberOfTrailingZeros(mask);
            for(int i=0;i<V;i++){
                if((mask&(1<<i))==0 || first==i) continue;
                for (int j = 0; j < V; j++)
                    if (adjList[i].contains(j) && (mask & (1<<j))!=0)
                        dp[mask][i] += dp[mask ^ (1 << i)][j];
            }
        }
        //Calculating simple cycles
        long count=0;
        for(int mask=1;mask<(1<<V);mask++) {
            int first = Integer.numberOfTrailingZeros(mask);
            if (Integer.bitCount(mask)>=3)
                for (int i = 0; i < V; i++) {
                    if(adjList[first].contains(i))
                        count+=dp[mask][i];
                }
        }
        return count/2;

    }
}


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

public class Main {

    private StreamTokenizer in;
    private BufferedWriter out;

    public void solve() throws Exception {
        int n = nextInt(), m = nextInt();
        int[] ss = new int[n];
        for (int i=0; i<m; i++)
        {
            int a = nextInt(), b = nextInt();
            a--;b--;
            ss[a]|=1<<b;
            ss[b]|=1<<a;
        }
        long[][] res = new long[n][1<<n];
        int[] cnt = new int[1<<n], first = new int[1<<n];
        for (int i=0; i<n; i++)
        {
            res[i][1<<i] = 1;
            first[1<<i] = i;
            cnt[1<<i] = 1;
        }
        long ans = 0;
        for (int mask = 0; mask<1<<n; mask++)
        {
            for (int last = first[mask]; last<n; last++)
            {
                if (res[last][mask]==0)
                    continue;
                if (cnt[mask]>2)
                {
                    if ((ss[last]&(1<<first[mask]))!=0)
                        ans+=res[last][mask];
                }
                int m2 = (~mask) & ss[last];
                for (int next = first[mask]+1; next<n; next++)
                {
                    if ((m2&(1<<next))==0) continue;
                    int mask2 = mask|1<<next;
                    res[next][mask2]+=res[last][mask];
                    cnt[mask2] = cnt[mask]+1;
                    first[mask2] = first[mask];
                }
            }
        }
        ans/=2;
        out.write(ans+"\n");
    }

    public int nextInt() throws Exception
    {
        in.nextToken();
        return (int)in.nval;
    }

    public void run() {
        try {
            in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
            out = new BufferedWriter(new OutputStreamWriter(System.out));
            solve();
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

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


import java.util.Scanner;

import static java.lang.Integer.bitCount;
import static java.lang.Integer.numberOfTrailingZeros;
import static java.lang.System.out;

/**
 * 11D
 *
 * @author artyom
 */
public class ASimpleTask {
    public static void main(String args[]) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[][] d = new int[n][n];
        for (int i = 0, m = sc.nextInt(); i < m; i++) {
            int a = sc.nextInt() - 1, b = sc.nextInt() - 1;
            d[a][b] = 1;
            d[b][a] = 1;
        }
        long[][] dp = new long[1 << n][n];

        // SOLUTION BEGINS
        for (int mask = 1; mask < 1 << n; mask++) {
            int start = numberOfTrailingZeros(mask); // the starting vertex of a Hamiltonian walk
            if (bitCount(mask) == 1) {
                dp[mask][start] = 1;
                continue;
            }
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) > 0 && i != start) {
                    int xmask = mask ^ (1 << i); // mask without vertex i
                    for (int j = 0; j < n; j++) {
                        if (d[j][i] > 0) {
                            dp[mask][i] += dp[xmask][j];
                        }
                    }
                }
            }
        }
        // SOLUTION ENDS

        long sum = 0;
        for (int mask = 1; mask < 1 << n; mask++) {
            if (bitCount(mask) >= 3) { // We need at least 3 vertices for a cycle
                for (int i = 0; i < n; i++) {
                    if (d[numberOfTrailingZeros(mask)][i] > 0) {
                        sum += dp[mask][i];
                    }
                }
            }
        }
        out.print(sum / 2);
    }
}


import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.StringTokenizer;

public class Main implements Runnable {

	int n;
	long[][] f;
	boolean[][] e;

	int bit(int value) {
		return (1<<value);
	}

	void solve() throws IOException {
		n = nextInt();
		int m = nextInt();
		f = new long[1<<n][n];
		e = new boolean[n][n];
		for (int i = 0; i < (1<<n); ++i) {
			for (int j = 0; j < n; ++j) {
				f[i][j] = -1;
			}
		}
		for (int i = 0; i < n; ++i) {
			for (int j = 0; j < n; ++j) {
				f[bit(i)|bit(j)][j] = 0;
				e[i][j] = false;
			}
		}
		for (int i = 0; i < m; ++i) {
			int u = nextInt()-1;
			int v = nextInt()-1;
			e[u][v] = true;
			e[v][u] = true;
			if (u < v) {
				f[bit(u)|bit(v)][v] = 1;
			}
			else {
				f[bit(v)|bit(u)][u] = 1;
			}
		}
		long answer = 0;
		for (int i = 1; i < (1<<n); ++i) {
			int start = 0;
			while (((1<<start)&i) == 0) {
				++start;
			}
			int s = bit(start);
			for (int nxt = start+1; nxt < n; ++nxt) {
				int b = bit(nxt);
				if ((b&i) > 0 && (b|s) != i) {
					if (e[start][nxt]) {
						answer += clc(i, nxt);
					}
				}
			}
		}
		writer.print(answer>>1);
	}

	long clc(int maska, int last) {
		if (f[maska][last] == -1) {
			int first = 0;
			while (((1<<first)&maska) == 0) {
				++first;
			}
			f[maska][last] = 0;
			for (int b = first+1; b < n; ++b) {
				if ((bit(b)&maska)>0) {
					if (e[b][last]) {
						f[maska][last] += clc(maska^bit(last), b);
					}
				}
			}
		}
		return f[maska][last];
	}

	public static void main(String[] args) throws InterruptedException {
		 new Thread(null, new Runnable() {
            public void run() {
                new Main().run();
            }
	     },
	     "1",
	     1 << 25).start();
	}

	@Override
	public void run() {
		try {
			boolean fromStandart = true;
			reader = new BufferedReader(fromStandart ? new InputStreamReader(System.in) : new FileReader(INFILE));
            writer = new PrintWriter(new BufferedWriter(fromStandart ? new OutputStreamWriter(System.out) : new FileWriter(OUTFILE)));
			tokenizer = null;
			solve();
			writer.flush();
		} catch (Exception error) {
			error.printStackTrace();
			System.exit(1);
		}
	}

	static final String INFILE = "input.txt";
	static final String OUTFILE = "output.txt";

	BufferedReader reader;
	StringTokenizer tokenizer;
    PrintWriter writer;

	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 nextString() throws IOException {
    	return reader.readLine();
    }

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

}


import java.io.*;
import java.util.*;
public class D11 {
	static HashMap<State, Integer> map;
	static long[][] ans;
	static boolean[][] connect;
	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());
		map = new HashMap<State, Integer>();
		connect = new boolean[n][n];
		ans = new long[n][1<<n];
		for(int i = 0; i < n; i++)
			Arrays.fill(ans[i], -1);
		int m = Integer.parseInt(st.nextToken());
		while(m-- > 0)	{
			st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			a--;
			b--;
			connect[a][b] = connect[b][a] = true;
		}
		long ret = 0;
		int mask = 1 << n;
		mask--;
		for(int i = 0; i < n; i++)	{
			for(int out = i+1; out < n; out++)	{
				if(connect[i][out])	{
					ret += solve(mask - (1<<out), out, true);
				}
			}
			mask -= (1<<i);
		}
		System.out.println(ret/2);
	}
	public static long solve(int mask, int start, boolean a)	{
		if(ans[start][mask] != -1)
			return ans[start][mask];
		int end = 0;
		while((mask & (1<<end)) == 0)
			end++;
		long ret = 0;
		for(int out = 0; out < connect.length; out++)	{
			if(connect[start][out] && (mask & (1 << out)) != 0)	{
				if(out == end)	{
					if(!a)
						ret++;
				}
				else
					ret += solve(mask - (1<<out), out, false);
			}
		}
		ans[start][mask] = ret;
		return ret;
	}
	static class State	{
		public byte start, go;
		public int mask;
		public State(byte a, byte b, int c)	{
			start = a;
			go = b;
			mask = c;
		}
		public int hashCode()	{
			return 10007*mask + 43 * start + go;
		}
		public boolean equals(Object o)	{
			State s = (State)o;
			return start == s.start && go == s.go && mask == s.mask;
		}
	}
}


import javafx.util.Pair;

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

public class ElongatedMatrix {
    private static int n;
    /**
     * if the rows are arranged in some permutation [p_1, p_2,...i,j,...p_n],
     * 1!=i!=j!=n, minCost[i][j] is the min k for any permutation with i&j adjacent
     **/
    private static int[][] minCost;
    /**
     * minCostEndpoints[i][j] is the min k for any permutation such that
     * the rows are arranged in some permutation [i, p_2, ... p_{n-1}, j],
     **/
    private static int[][] minCostEndpoints;

    /**
     * Max k of a path over a subset of all rows ending at a certain vertex
     * The first 16 bits of the key represent the subset. Node n is in the subset iff (x & (2 << n))!=0
     * key/(1<<16) is the end vertex number
     */
    private static HashMap<Integer, Integer> costs = new HashMap<>();

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int[] params = Arrays.stream(br.readLine().split(" "))
                .mapToInt(x -> Integer.parseInt(x)).toArray();
        n = params[0];
        int m = params[1];
        int[][] matrix = new int[n][m];
        for (int i = 0; i < n; i++) {
            matrix[i] = Arrays.stream(br.readLine().split(" "))
                    .mapToInt(x -> Integer.parseInt(x)).toArray();
        }
        minCost = new int[n][n];
        minCostEndpoints = new int[n][n];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                if (i > j) {
                    minCost[i][j] = Integer.MAX_VALUE;
                    for (int k = 0; k < m; k++) {
                        int diff = Math.abs(matrix[i][k] - matrix[j][k]);
                        if (diff < minCost[i][j]) {
                            minCost[i][j] = diff;
                        }
                    }
                    minCost[j][i] = minCost[i][j];
                }
                minCostEndpoints[i][j] = Integer.MAX_VALUE;
                for (int k = 0; k < m - 1; k++) {
                    int diff = Math.abs(matrix[i][k + 1] - matrix[j][k]);
                    if (diff < minCostEndpoints[i][j]) {
                        minCostEndpoints[i][j] = diff;
                    }
                }
            }
        }
        int maxCost = n == 1 ? minCostEndpoints[0][0] : 0;
        for (int i = 0; i < n; i++) {
            costs.clear();
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    int bitmask = (1 << i) | (1 << j);
                    int state = bitmask + (j << 16);
                    costs.put(state, minCost[i][j]);
                }
            }
            for (int j = 0; j < n; j++) {
                if (i != j) {
                    if (minCostEndpoints[i][j] <= maxCost) {
                        continue;
                    } else {
                        int pathCost = Math.min(minCostEndpoints[i][j], findMaxCost(i, j, (1 << n) - 1));
                        maxCost = Math.max(maxCost, pathCost);
                    }
                }
            }
        }
        System.out.println(maxCost);
        br.close();
    }

    //find the minimum cost starting at st, ending at end, and using only the rows in set
    private static int findMaxCost(int st, int end, int set) {
        int state = set + (end << 16);
        if (costs.containsKey(state)) {
            return costs.get(state);
        }
        int maxCost = 0;
        for (int i = 0; i < n; i++) {
            if (i != st && i != end && (set & (1 << i)) != 0) {
                int setWithoutEnd = set - (1 << end);
                int pathCost = Math.min(findMaxCost(st, i, setWithoutEnd), minCost[i][end]);
                maxCost = Math.max(pathCost, maxCost);
            }
        }
        costs.put(state, maxCost);
        return maxCost;
    }
}


import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class ElongatedMatrix {
	static int[][][] memo;
	static int mn1[][];
	static int mn2[][];
	static int r, c;

	static int dp(int mask, int first, int lastvisited) {
		if (memo[first][lastvisited][mask] != -1)
			return memo[first][lastvisited][mask];
		int ans = 0;
		for (int i = 0; i < r; i++) {
			if ((mask & (1 << i)) == 0) {
				if (Integer.bitCount(mask) != r - 1) {
					ans = Math.max(ans, Math.min(mn1[lastvisited][i], dp(((mask) | (1 << i)), first, i)));
				} else
					ans = Math.max(ans, Math.min(mn2[first][i], mn1[lastvisited][i]));
			}
		}
		//System.out.println(ans);
		return memo[first][lastvisited][mask] = ans;
	}

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st = new StringTokenizer(br.readLine());
		PrintWriter out = new PrintWriter(System.out);
		r = Integer.parseInt(st.nextToken());

		c = Integer.parseInt(st.nextToken());
		int[][] arr = new int[r][c];
		memo = new int[r][r][1 << r];
		mn1 = new int[r][r];
		mn2 = new int[r][r];
		for(int i=0;i<r;i++) {
			st=new StringTokenizer(br.readLine());
			for(int j=0;j<c;j++) {
				arr[i][j]=Integer.parseInt(st.nextToken());
			}
		}
		for (int i = 0; i < r; i++) {
			Arrays.fill(mn1[i], (int)1e9);
			Arrays.fill(mn2[i], (int)1e9);
		}

		for (int i = 0; i < r; i++) {
			for (int j = 0; j < r; j++) {
				Arrays.fill(memo[i][j], -1);
			}
		}
		for (int i = 0; i < r; i++) {
			for (int j = 0; j < r; j++) {
				for (int k = 0; k < c; k++) {

					mn1[i][j] = Math.min(mn1[i][j], Math.abs(arr[i][k] - arr[j][k]));

				}
			}
		}
		for (int i = 0; i < r; i++) {
			for (int j = 0; j < r; j++) {
				for (int k = 0; k < c-1; k++) {

					mn2[i][j] = Math.min(mn2[i][j], Math.abs(arr[j][k] - arr[i][k + 1]));

				}
			}
		}

		int ans = 0;
		for (int i = 0; i < r; i++) {
			ans=Math.max(ans, dp(1<<i,i,i));
		}
		if(r==1)
			ans=mn2[0][0];
		out.println(ans);
		out.flush();
	}

}


//package ;
import java.io.*;
import java.util.*;

public class D {
	static int dp(int i,int start,int msk)
	{
		if(Integer.bitCount(msk)==n)
			return s_e[i][start];

		if(dp[i][start][msk]!=-1)
			return dp[i][start][msk];

		int max=0;
		for(int k=0;k<n;k++)
		{
			int min=Integer.MAX_VALUE;
			if((msk & (1<<k)) == 0 )
			{
				min=diff[i][k];
				min=Math.min(min, dp(k,start,msk | (1<<k)));
				max=Math.max(max, min);
			}
		}
		return dp[i][start][msk]=max;
	}
	static int n,m,a[][],dp[][][],diff[][],s_e[][];
	public static void main(String[] args) throws IOException {
		Scanner sc = new Scanner();
		PrintWriter pw = new PrintWriter(System.out);
		n=sc.nextInt();
		m=sc.nextInt();
		a=new int[n][m];
		diff=new int[n][n];
		s_e=new int[n][n];
		for(int i=0;i<n;i++)
			for(int j=0;j<m;j++)
				a[i][j]=sc.nextInt();

		dp=new int[n][n][70000];
		int ans=0;
		for(int i=0;i<n;i++)
			for(int j=0;j<n;j++)
			{
				Arrays.fill(dp[i][j], -1);
				diff[i][j]=Integer.MAX_VALUE;
				s_e[i][j]=Integer.MAX_VALUE;
				for(int k=0;k<m-1;k++)
				{
					diff[i][j]=Math.min(Math.abs(a[i][k]-a[j][k]), diff[i][j]);
					s_e[i][j]=Math.min(Math.abs(a[i][k]-a[j][k+1]), s_e[i][j]);
				}
				diff[i][j]=Math.min(Math.abs(a[i][m-1]-a[j][m-1]), diff[i][j]);
			}

		for(int i=0;i<n;i++)
			ans=Math.max(ans, dp(i,i,1<<i));

		pw.print(ans);
		pw.close();
	}

	static class Scanner {
		BufferedReader br;
		StringTokenizer st;

		Scanner() {
			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());
		}

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

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

		String nextLine() throws IOException {
			return br.readLine();
		}

		boolean hasnext() throws IOException {
			return br.ready();
		}

	}
}


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

public class Main implements Runnable {
    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private SpaceCharFilter filter;
        private BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        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 String nextLine() {
            String str = "";
            try {
                str = br.readLine();
            }
            catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
        public int nextInt() {
            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 nextLong() {
            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 double nextDouble() {
            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 == 'e' || c == 'E')
                    return res * Math.pow(10, nextInt());
                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 == 'e' || c == 'E')
                        return res * Math.pow(10, nextInt());
                    if (c < '0' || c > '9')
                        throw new InputMismatchException();
                    m /= 10;
                    res += (c - '0') * m;
                    c = read();
                }
            }
            return res * sgn;
        }

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

            return res.toString();
        }

        public boolean isSpaceChar(int c) {
            if (filter != null)
                return filter.isSpaceChar(c);
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public String next() {
            return readString();
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);
        }
    }
    public static void main(String args[]) throws Exception {
        new Thread(null, new Main(),"Main",1<<26).start();
    }

    static long gcd(long a,long b){ if(b==0)return a;return gcd(b,a%b); }
    static long modPow(long a,long p,long m){ if(a==1)return 1;long ans=1;while (p>0){ if(p%2==1)ans=(ans*a)%m;a=(a*a)%m;p>>=1; }return ans; }
    static long modInv(long a,long m){return modPow(a,m-2,m);}
    static long sol_x,sol_y,gcd_a_b;
    static void extendedEuclid(long a,long b){ if(b==0){gcd_a_b=a;sol_x=1;sol_y=0; } else{ extendedEuclid(b,a%b);long temp = sol_x;sol_x=sol_y;sol_y = temp - (a/b)*sol_y; } }
    static class Bhavansort{ Random random;Bhavansort(int a[]){ randomArray(a); sort(a);}Bhavansort(long a[]){ randomArray(a); sort(a);}static int[] sort(int a[]){ Arrays.sort(a);return a;}static long[] sort(long a[]){ Arrays.sort(a);return a; }void randomArray(long a[]){ int n=a.length;for(int i=0;i<n;i++){ int p=random.nextInt(n)%n;long tm=a[i];a[i]=a[p];a[p]=tm; } }void randomArray(int a[]){ int n=a.length;for(int i=0;i<n;i++){ int p=random.nextInt(n)%n;int tm=a[i];a[i]=a[p];a[p]=tm; } }}

    public void run() {
        InputReader sc = new InputReader(System.in);
        PrintWriter out = new PrintWriter(System.out);

        int n=sc.nextInt();
        int m=sc.nextInt();
        int a[][]=new int[n][m];
        for (int i = 0; i <n ; i++) {
            for (int j = 0; j <m ; j++) {
                a[i][j]=sc.nextInt();
            }
        }

        int cost[][]=new int[n][n];

        for (int i = 0; i < n; i++) {
            for (int j = 0; j <n ; j++) {
                cost[i][j]=Integer.MAX_VALUE;
                for (int k = 0; k <m ; k++) {
                    cost[i][j]=Math.min(cost[i][j],Math.abs(a[i][k]-a[j][k]));
                }
            }
        }

        int costRight[][]=new int[n][n];
        for (int i = 0; i <n ; i++) {
            for (int j = 0; j <n ; j++) {
                costRight[i][j]=Integer.MAX_VALUE;
                for (int k = 0; k <m-1 ; k++) {
                    costRight[i][j]=Math.min(Math.abs(a[i][k+1]-a[j][k]),costRight[i][j]);
                }
            }
        }

        /*for (int i = 0; i <n ; i++) {
            for (int j = 0; j <n ; j++) {
                out.print(cost[i][j]+" ");
            }
            out.println();
        }
        out.println();

        for (int i = 0; i <n ; i++) {
            for (int j = 0; j <n ; j++) {
                out.print(costRight[i][j]+" ");
            }
            out.println();
        }*/

        if(n==1){
            int ans=Integer.MAX_VALUE;
            for (int i = 0; i <m-1 ; i++) {
                ans=Math.min(ans,Math.abs(a[0][i]-a[0][i+1]));
            }
            out.println(ans);
            out.close();
            return;
        }

        Long dp[][][]=new Long[n+1][n+1][1<<n];
        long max=0;
        for (int i = 0; i <n ; i++) {
           // out.println(f(i,i,1<<i,dp,n,cost,costRight));
            max=Math.max(max,f(i,i,1<<i,dp,n,cost,costRight));
        }

        out.println(max);
        out.close();
    }

    long f(int start,int end,int mask,Long dp[][][],int n,int cost[][],int costRight[][]){
        if(dp[start][end][mask]!=null)return dp[start][end][mask];
        long ans=Integer.MIN_VALUE;
        for (int i = 0; i <n ; i++) {
            if((mask&(1<<i))==0){
                int newMask=mask|(1<<i);
                if((1<<n)-1!=(mask|(1<<i))){
                    ans = Math.max(ans, Math.min(cost[end][i], f(start, i, newMask, dp, n, cost, costRight)));
                }
                else{
                    ans=Math.max(ans,Math.min(cost[end][i],costRight[start][i]));
                }
            }
        }
        return dp[start][end][mask]=ans;
    }

}


import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.StringTokenizer;

public class ElongatedMatrix {
	public static void main(String[] args) {
		InputStream input;
		OutputStream output;
		try {
			input = new FileInputStream("input.txt");
			output = new FileOutputStream("output.txt");
		} catch (FileNotFoundException e) {
			input = System.in;
			output = System.out;
		}
		Kattio io = new Kattio(input, output);
		(new Solve(io)).main();
		io.close();

		if (input instanceof FileInputStream)
			try {
				input.close();
			} catch (IOException e) {

			}
		if (output instanceof FileOutputStream)
			try {
				output.close();
			} catch (IOException e) {

			}
	}
}

class Solve {
	static final int oo = (int) 1e9;
	Kattio io;
	int n, m;
	int[][] a;
	int[][][] dp;
	int[][] diff;
	int[][] slant;

	Solve(Kattio io) {
		this.io = io;
	}

	int getbit(int x, int n) {
		n--;
		return (x >> n) & 1;
	}

	int setbit(int x, int n) {
		n--;
		return x | (1 << n);
	}

	int caldp(int currentRow, int firstRow, int mask) {
		if (dp[currentRow][firstRow][mask] != -1)
			return dp[currentRow][firstRow][mask];
		dp[currentRow][firstRow][mask] = 0;
		if (mask == (1 << n) - 1)
			dp[currentRow][firstRow][mask] = slant[currentRow][firstRow];
		else {
			for (int i = 1; i <= n; i++)
				if (getbit(mask, i) == 0) {
					dp[currentRow][firstRow][mask] = Math.max(
							Math.min(caldp(i, firstRow, setbit(mask, i)), diff[currentRow][i]),
							dp[currentRow][firstRow][mask]);

				}
		}
		return dp[currentRow][firstRow][mask];

	}

	void main() {
		n = io.getInt();
		m = io.getInt();
		a = new int[n+1][m+1];
		dp = new int[n+1][n+1][1<<n];

		for (int i=1; i<=n; i++)
			for (int j=1; j<=m; j++)
				a[i][j] = io.getInt();

		diff = new int[n+1][n+1];
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
			{
				diff[i][j]=oo;
				for (int x=1; x<=m; x++)
					diff[i][j]=Math.min(diff[i][j], Math.abs(a[i][x]-a[j][x]));
			}

		slant = new int[n+1][n+1];
		for (int i=1; i<=n; i++)
			for (int j=1; j<=n; j++)
			{
				slant[i][j] = oo;
				for (int x=1; x+1<=m; x++)
					slant[i][j] = Math.min(slant[i][j], Math.abs(a[i][x]-a[j][x+1]));
			}

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

		int res = 0;
		for (int i=1; i<=n; i++)
			res = Math.max(res, caldp(i,i,setbit(0,i)));

		io.print(res);
	}
}

class Kattio extends PrintWriter {
	public Kattio(InputStream i) {
		super(new BufferedOutputStream(System.out));
		r = new BufferedReader(new InputStreamReader(i));
	}

	public Kattio(InputStream i, OutputStream o) {
		super(new BufferedOutputStream(o));
		r = new BufferedReader(new InputStreamReader(i));
	}

	public boolean hasMoreTokens() {
		return peekToken() != null;
	}

	public int getInt() {
		return Integer.parseInt(nextToken());
	}

	public double getDouble() {
		return Double.parseDouble(nextToken());
	}

	public long getLong() {
		return Long.parseLong(nextToken());
	}

	public String getWord() {
		return nextToken();
	}

	private BufferedReader r;
	private String line;
	private StringTokenizer st;
	private String token;

	private String peekToken() {
		if (token == null)
			try {
				while (st == null || !st.hasMoreTokens()) {
					line = r.readLine();
					if (line == null)
						return null;
					st = new StringTokenizer(line);
				}
				token = st.nextToken();
			} catch (IOException e) {
			}
		return token;
	}

	private String nextToken() {
		String ans = peekToken();
		token = null;
		return ans;
	}
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Scanner;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        Scanner in = new Scanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskF solver = new TaskF();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskF {
        public void solve(int testNumber, Scanner in, PrintWriter out) {
            int n = in.nextInt();
            int m = in.nextInt();
            int[][] graph = new int[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++)
                    graph[i][j] = in.nextInt();
            }
            int[][] mn1 = new int[n][n];
            int[][] mn2 = new int[n][n];
            for (int i = 0; i < n; i++)
                for (int j = 0; j < n; j++) {
                    int min_val = Integer.MAX_VALUE;
                    for (int k = 0; k < m; k++)
                        min_val = Math.min(min_val, Math.abs(graph[i][k] - graph[j][k]));
                    mn1[i][j] = min_val;
                    min_val = Integer.MAX_VALUE;
                    for (int k = 0; k < m - 1; k++) {
                        min_val = Math.min(min_val, Math.abs(graph[i][k] - graph[j][k + 1]));
                    }
                    mn2[i][j] = min_val;
                }
            int[][] dp = new int[(1 << (n + 2))][n];
            int ans = 0;
            for (int i = 0; i < n; i++) {
                for (int[] temp : dp)
                    Arrays.fill(temp, -1);
                for (int j = 0; j < n; j++) {
                    dp[1 << j][j] = (j == i ? Integer.MAX_VALUE : 0);
//                dp[1 << j][j] = (j == i ? 0: Integer.MAX_VALUE );
                }
                for (int j = 0; j < n; j++) {
                    ans = Math.max(ans, Math.min(mn2[j][i], calc((1 << n) - 1, j, dp, mn1, n)));
                }
            }
            out.println(ans);
        }

        public int calc(int mask, int v, int[][] dp, int[][] mn1, int n) {
//        System.out.println(mask+","+v);
            if (dp[mask][v] != -1)
                return dp[mask][v];
            dp[mask][v] = 0;
            for (int u = 0; u < n; u++) {
                if (u != v && (((mask >> u) & 1) == 1))
                    dp[mask][v] = Math.max(dp[mask][v], Math.min(mn1[u][v], calc(mask ^ (1 << v), u, dp, mn1, n)));
            }
            return dp[mask][v];
        }

    }
}