import java.util.*;
import java.io.*;
import static java.lang.Math.*;
public class Main {
    static final long MOD = 1_000_000_007, INF = 1_000_000_000_000_000_000L;
    static final int INf = 1_000_000_000;
    static FastReader reader;
    static PrintWriter writer;
    public static void main(String[] args) {
        Thread t = new Thread(null, new O(), "Integer.MAX_VALUE", 100000000);
        t.start();
    }
    static class O implements Runnable {
        public void run() {
            try {
                magic();
            }
            catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
    static int n,m,pre[][], pre_stop_and_start[][],mat[][], dp[][][];
    static void magic() throws IOException {
        reader = new FastReader();
        writer = new PrintWriter(System.out, true);
        n = reader.nextInt();
        m = reader.nextInt();
        mat = new int[n][m];
        for(int i=0;i<n;++i) {
            for(int j=0;j<m;++j) {
                mat[i][j] = reader.nextInt();
            }
        }
        if(n==1) {
            int ans = Integer.MAX_VALUE;
            for(int i=0;i+1<m;++i) {
                ans = min(ans, abs(mat[0][i] - mat[0][i+1]));
            }
            writer.println(ans);
            System.exit(0);
        }
        pre = new int[n][n];
        pre_stop_and_start = new int[n][n];
        for(int i=0;i<n;++i) {
            for(int j=i+1;j<n;++j) {
                int min = Integer.MAX_VALUE;
                for(int k=0;k<m;++k) {
                    min = min(min, abs(mat[i][k] - mat[j][k]));
                }
                pre[i][j] = pre[j][i] = min;
            }
        }
        for(int i=0;i<n;++i) {
            for(int j=0;j<n;++j) {
                if(j==i) {
                    continue;
                }
                int min = Integer.MAX_VALUE;
                for(int k=0;k+1<m;++k) {
                    min = min(min, abs(mat[j][k+1] - mat[i][k]));
                }
                pre_stop_and_start[i][j] = min;
            }
        }
//        writer.println("Pre array: ");
//        for(int i=0;i<n;++i) {
//            for(int j=0;j<n;++j) {
//                writer.print(pre[i][j]+" ");
//            }
//            writer.println();
//        }
//        writer.println("Pre stop and start array: ");
//        for(int i=0;i<n;++i) {
//            for(int j=0;j<n;++j) {
//                if(j==i) {
//                    writer.print("SKIP ");
//                }
//                else writer.print(pre_stop_and_start[i][j]+" ");
//            }
//            writer.println();
//        }
        dp = new int[1<<n][n][n];
        for(int i=0;i<(1<<n);++i) {
            for(int j=0;j<n;++j) {
                for(int k=0;k<n;++k) {
                    dp[i][j][k] = -1;
                }
            }
        }
        int ans = 0;
        for(int i=0;i<n;++i) {
            ans = max(ans, f((1<<i), i, i));
        }
        writer.println(ans);
    }
    static int f(int mask_already, int prev, int first) {
        if(mask_already==(1<<n) - 1) {
            return pre_stop_and_start[prev][first];
        }
        if(dp[mask_already][prev][first] != -1) {
            return dp[mask_already][prev][first];
        }
        int max = 0;
        for(int i=0;i<n;++i) {
            if((mask_already&(1<<i)) == 0) {
                max = max(max, min(pre[prev][i], f(mask_already|(1<<i), i, first)));
            }
        }
        return dp[mask_already][prev][first] = max;
    }
}


/*
If you want to aim high, aim high
Don't let that studying and grades consume you
Just live life young
******************************
What do you think? What do you think?
1st on Billboard, what do you think of it
Next is a Grammy, what do you think of it
However you think, I’m sorry, but shit, I have no fcking interest
*******************************
I'm standing on top of my Monopoly board
That means I'm on top of my game and it don't stop
til my hip don't hop anymore
https://www.a2oj.com/Ladder16.html
*******************************
300IQ as writer = Sad!
*/
import java.util.*;
import java.io.*;
import java.math.*;

   public class F
   {
      public static void main(String hi[]) throws Exception
      {
         BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
         StringTokenizer st = new StringTokenizer(infile.readLine());
         int N = Integer.parseInt(st.nextToken());
         int M = Integer.parseInt(st.nextToken());
         int[][] grid = new int[N][M];
         for(int i=0; i < N; i++)
            grid[i] = readArr(M, infile, st);
         int[][] mindiff = new int[N][N];
         for(int a=0; a < N; a++)
            for(int b=a+1; b < N; b++)
            {
               int val = Integer.MAX_VALUE;
               for(int i=0; i < M; i++)
                  val = Math.min(val, Math.abs(grid[a][i]-grid[b][i]));
               mindiff[a][b] = mindiff[b][a] = val;
            }
         int res = 0;
         for(int start=0; start < N; start++)
         {
            int[][] dp = new int[1<<N][N];
            Arrays.fill(dp[0], Integer.MAX_VALUE);
            for(int mask=0; mask < (1<<N); mask++)
            {
               if(Integer.bitCount(mask) == 1 && mask != (1<<start))
                  continue;
               for(int prev=0; prev < N; prev++)
                  if((mask&(1<<prev)) > 0 || mask == 0)
                  {
                     for(int b=0; b < N; b++)
                        if((mask&(1<<b)) == 0)
                        {
                           int submask = mask|(1<<b);
                           if(mask == 0)
                              dp[submask][b] = Integer.MAX_VALUE;
                           else
                              dp[submask][b] = Math.max(dp[submask][b], Math.min(dp[mask][prev], mindiff[prev][b]));
                        }
                  }
            }
            for(int b=0; b < N; b++)
            {
               int temp = dp[(1<<N)-1][b];
               for(int i=0; i < M-1; i++)
                  temp = Math.min(temp, Math.abs(grid[b][i]-grid[start][i+1]));
               res = Math.max(res, temp);
            }
         }
         System.out.println(res);
      }
      public static int[] readArr(int N, BufferedReader infile, StringTokenizer st) throws Exception
      {
         int[] arr = new int[N];
         st = new StringTokenizer(infile.readLine());
         for(int i=0; i < N; i++)
            arr[i] = Integer.parseInt(st.nextToken());
         return arr;
      }
   }


import java.io.*;
import java.util.*;
public class EMatrix{
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskE solver = new TaskE();
        solver.solve(1, in, out);
        out.flush();out.close();
    }
        static class TaskE {
            final int max = (int)(1E9);
            int n , m;
            int a[][];
            int gm[];
            boolean visit[][]; int dp[][];
            boolean check(int d){
                if(n == 1){
                    for(int i = 0; i < m - 1; i++){
                        if(Math.abs(a[0][i] - a[0][i + 1]) < d)return false;
                    }
                    return true;
                }
                int nm[] = new int[n], pm[] = new int[n];
                for(int i = 0; i < n; i++){
                    boolean r;
                    for(int j = 0; j < n; j++){
                        if(j == i)continue;
                        r = true;
                        for(int k = 0; k < m; k++){
                            if(Math.abs(a[i][k] - a[j][k]) < d){
                                r = false; break;
                            }
                        }
                        if(r){
                            nm[i] |= (1 << j);
                        }
                        r = true;
                        for(int k = 0; k < m - 1; k++){
                            if(Math.abs(a[i][k + 1] - a[j][k]) < d){
                                r = false; break;
                            }
                        }
                        if(r){
                            pm[i] |= (1 << j);
                        }
                    }
                }
                // for(int i = 0; i < n; i++){
                //     System.out.println(nm[i] + " " + pm[i]);
                // }
                for(int i = 0; i < n; i++){
                    gm = new int[n];
                    gm[i] = nm[i];
                    for(int j = 0; j < n; j++){
                        if(j == i)continue;
                        if((nm[j] & (1 << i)) != 0){
                            gm[j] = nm[j] ^ (1 << i);
                        }else{
                            gm[j] = nm[j];
                        }
                    }
                    for(int j = 0; j < n; j++){
                        if(j == i)continue;
                        if((pm[i] >> j) % 2 == 1){
                            gm[j] |= (1 << i);
                        }
                    }
                    visit = new boolean[n][1 << n]; dp = new int[n][1 << n];
                    // for(int x = 0; x < n; x++)System.out.println(gm[x]);
                    if(dfs(i, i, (1 << i)) == n){
                        return true;
                    }
                }
                return false;
            }
            int dfs(int u, int r, int mask){
                // System.out.println(u + " " + r + " " + mask);
                if(u == r && mask == (1 << n) - 1)return 0;
                if(visit[u][mask])return dp[u][mask];
                visit[u][mask] = true;
                int val = 0;
                for(int i = 0; i < n; i++){
                    if((gm[u] >> i) % 2 == 1 && ((i == r && mask == (1 << n) - 1) || (mask >> i) % 2 != 1)){
                        val = Math.max(val, 1 + dfs(i, r, mask | (1 << i)));
                    }
                }
                // System.out.println(u + " " + mask + " " + val);
                return dp[u][mask] = val;
            }
           public void solve(int testNumber, InputReader in, PrintWriter out) {
            n = in.nextInt(); m = in.nextInt();
            a = new int[n][m];
            for(int i = 0; i < n; i++){
                for(int j = 0; j < m; j++){
                    a[i][j] = in.nextInt();
                }
            }
            int l = 0, r = max, ans = 0;
            while(l <= r){
                int m = (l + r) >> 1;
                if(check(m)){
                    ans = m;
                    l = m + 1;
                }else{
                    r = m - 1;
                }
            }
            out.println(ans);
          }
//         pair ja[][];long w[];int from[],to[],c[];
//             void make(int n,int m,InputReader in){
//              ja=new pair[n+1][];w=new long[m];from=new int[m];to=new int[m];c=new int[n+1];
//              for(int i=0;i<m;i++){
//               int u=in.nextInt(),v=in.nextInt();long wt=in.nextLong();
//               c[u]++;c[v]++;from[i]=u;to[i]=v;w[i]=wt;
//              }
//              for(int i=1;i<=n;i++){
//               ja[i]=new pair[c[i]];c[i]=0;
//              }
//              for(int i=0;i<m;i++){
//               ja[from[i]][c[from[i]]++]=new pair(to[i],w[i]);
//               ja[to[i]][c[to[i]]++]=new pair(from[i],w[i]);
//              }
//             }
//        int[] radixSort(int[] f){ return radixSort(f, f.length); }
//    int[] radixSort(int[] f, int n)
//    {
//        int[] to = new int[n];
//        {
//            int[] b = new int[65537];
//            for(int i = 0;i < n;i++)b[1+(f[i]&0xffff)]++;
//            for(int i = 1;i <= 65536;i++)b[i]+=b[i-1];
//            for(int i = 0;i < n;i++)to[b[f[i]&0xffff]++] = f[i];
//            int[] d = f; f = to;to = d;
//        }
//        {
//            int[] b = new int[65537];
//            for(int i = 0;i < n;i++)b[1+(f[i]>>>16)]++;
//            for(int i = 1;i <= 65536;i++)b[i]+=b[i-1];
//            for(int i = 0;i < n;i++)to[b[f[i]>>>16]++] = f[i];
//            int[] d = f; f = to;to = d;
//        }
//        return f;
//    }
    }
    static class InputReader {
        BufferedReader br;
        StringTokenizer st;
        public InputReader(InputStream stream) {
            br = new BufferedReader(new InputStreamReader(stream));
            st = null;
        }

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

        boolean hasMoreTokens() {
            while (st == null || !st.hasMoreTokens()) {
                String s = null;
                try {
                    s = br.readLine();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (s == null)
                    return false;
                st = new StringTokenizer(s);
            }
            return true;
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }
        public long nextLong() {
            return Long.parseLong(next());
        }
        public double nextDouble() {
            return Double.parseDouble(next());
        }
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.FilterInputStream;
import java.io.BufferedInputStream;
import java.io.InputStream;

/**
 * @author khokharnikunj8
 */

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

    void solve() {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        ScanReader in = new ScanReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        FElongatedMatrix solver = new FElongatedMatrix();
        solver.solve(1, in, out);
        out.close();
    }

    static class FElongatedMatrix {
        int[][] G;
        int[][] G1;

        public int findIt(int[] map, int n, int start) {
            int[][] mask = new int[1 << n][n];
            for (int i = 0; i < n; i++) mask[(1 << i)][i] = G[start][map[i]];
            for (int i = 1; i < (1 << n); i++) {
                for (int j = 0; j < n; j++) {
                    for (int k = 0; k < n; k++) {
                        if (k != j && (i & (1 << k)) == 0 && (i & (1 << j)) != 0) {
                            mask[(i | (1 << k))][k] = Math.max(mask[(i | (1 << k))][k], Math.min(mask[i][j], G[map[j]][map[k]]));
                        }
                    }
                }
            }
            int ans = 0;
            for (int i = 0; i < n; i++) ans = Math.max(ans, Math.min(mask[(1 << n) - 1][i], G1[start][map[i]]));
            return ans;
        }

        public void solve(int testNumber, ScanReader in, PrintWriter out) {
            int n = in.scanInt();
            int m = in.scanInt();
            G = new int[n][n];
            G1 = new int[n][n];
            int[][] ar = new int[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    ar[i][j] = in.scanInt();
                }
            }
            if (n == 1) {
                int ans = Integer.MAX_VALUE;
                for (int i = 0; i < m - 1; i++) ans = Math.min(ans, Math.abs(ar[0][i] - ar[0][i + 1]));
                out.println(ans);
                return;
            }
            for (int i = 0; i < n; i++) {
                for (int j = i + 1; j < n; j++) {
                    int min = Integer.MAX_VALUE;
                    for (int k = 0; k < m; k++) min = Math.min(min, Math.abs(ar[i][k] - ar[j][k]));
                    G[i][j] = G[j][i] = min;
                }
            }
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    if (i == j) continue;
                    int min = Integer.MAX_VALUE;
                    for (int k = 1; k < m; k++) min = Math.min(min, Math.abs(ar[i][k] - ar[j][k - 1]));
                    G1[i][j] = min;

                }
            }
            int[] map;
            int ans = 0;
            for (int i = 0; i < n; i++) {
                map = new int[n - 1];
                int tl = 0;
                for (int temp = 0; temp < n; temp++) {
                    if (temp == i) continue;
                    map[tl++] = temp;
                }
                ans = Math.max(ans, findIt(map, n - 1, i));

            }
            out.println(ans);

        }

    }

    static class ScanReader {
        private byte[] buf = new byte[4 * 1024];
        private int index;
        private BufferedInputStream in;
        private int total;

        public ScanReader(InputStream inputStream) {
            in = new BufferedInputStream(inputStream);
        }

        private int scan() {
            if (index >= total) {
                index = 0;
                try {
                    total = in.read(buf);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (total <= 0) return -1;
            }
            return buf[index++];
        }

        public int scanInt() {
            int integer = 0;
            int n = scan();
            while (isWhiteSpace(n)) n = scan();
            int neg = 1;
            if (n == '-') {
                neg = -1;
                n = scan();
            }
            while (!isWhiteSpace(n)) {
                if (n >= '0' && n <= '9') {
                    integer *= 10;
                    integer += n - '0';
                    n = scan();
                }
            }
            return neg * integer;
        }

        private boolean isWhiteSpace(int n) {
            if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
            else return false;
        }

    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.*;
public class F {
    public static void main(String[] args) {
        FastScanner scanner = new FastScanner();
        int N = scanner.nextInt();
        int M = scanner.nextInt();
        int[][] matrix = new int[N][M];
        for(int i = 0; i < N; i++) {
            for(int j = 0; j < M; j++) {
                matrix[i][j] = scanner.nextInt();
            }
        }
        int[][] maxDist = new int[N][N];
        for(int i = 0; i < N; i++) {
            Arrays.fill(maxDist[i], Integer.MAX_VALUE);
        }
        for(int i = 0; i < M; i++) {
            for(int  j = 0; j < N; j++) {
                for(int k = j+1; k < N; k++) {
                    maxDist[j][k] = Math.min(maxDist[j][k], Math.abs(matrix[k][i] - matrix[j][i]));
                    maxDist[k][j] = maxDist[j][k];
                }
            }
        }
        int[][] distTop = new int[N][N];
        for(int i = 0; i < N; i++) {
            Arrays.fill(distTop[i], Integer.MAX_VALUE);
        }
        for(int i = 0; i < M-1; i++) {
            for(int j = 0; j < N; j++) {
                for(int k = 0; k < N; k++) {
                    distTop[j][k] = Math.min(distTop[j][k], Math.abs(matrix[j][i] - matrix[k][i+1]));
                }
            }
        }
        if (N == 1) {
            System.out.println(distTop[0][0]);
            System.exit(0);
        }
        int[] bitLoc = new int[1<<N];
        for(int i = 0; i < N; i++) {
            bitLoc[1 << i] = i;
        }
        int[][][] dp = new int[1<<N][N][N];
        //iterate over every row
        for(int mask = 1; mask < (1 << N); mask++) {
            for(int smask = mask; smask > 0; smask &= (smask-1)) {
                int i = bitLoc[Integer.lowestOneBit(smask)];
                for (int ss = mask ^ 1 << i; ss > 0; ss &= ss - 1) {
                    int j = bitLoc[Integer.lowestOneBit(ss)];
                    if (mask == (1 << i ^ 1 << j))
                        dp[mask][i][j] = maxDist[i][j];
                    else {
                        int x = 0;
                        for (int sss = mask ^ 1 << i ^ 1 << j; sss > 0; sss &= sss - 1) {
                            int k = bitLoc[sss & -sss];
                            x = Math.max(x, Math.min(dp[mask ^ 1 << j][i][k], maxDist[k][j]));
                        }
                        dp[mask][i][j] = x;
                    }
                }
            }
        }
        int mxMsk = (1 << N) -1;
        int max = 0;
        for(int i = 0; i < N; i++) {
            for(int j = 0; j < N; j++) {
                if (i==j) continue;
                max = Math.max(max, Math.min(dp[mxMsk][i][j], distTop[i][j]));
            }
        }
        System.out.println(max);
    }
    public static class FastScanner {
        BufferedReader br;
        StringTokenizer st;
        public FastScanner(Reader in) {
            br = new BufferedReader(in);
        }
        public FastScanner() {
            this(new InputStreamReader(System.in));
        }
        String next() {
            while (st == null || !st.hasMoreElements()) {
                try {
                    st = new StringTokenizer(br.readLine());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return st.nextToken();
        }
        int nextInt() {
            return Integer.parseInt(next());
        }
        long nextLong() {
            return Long.parseLong(next());
        }
        double nextDouble() {
            return Double.parseDouble(next());
        }
        String readNextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
        int[] readIntArray(int n) {
            int[] a = new int[n];
            for (int idx = 0; idx < n; idx++) {
                a[idx] = nextInt();
            }
            return a;
        }
    }
}


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

public class E
{
	private static final int oo = 1000000000;
	public static void main(String[] args) throws Exception
	{
		Scanner in = new Scanner(System.in);
		int n = in.nextInt();
		int m = in.nextInt();
		if(n > m)
		{
			int t = n;
			n = m;
			m = t;
		}

		int [][] curr = new int[1<<n][1<<n];
		fill(curr, oo);
		Arrays.fill(curr[0], 0);

		for(int j = 0 ; j < m ; j++)
		{
			int [][] next = new int[1<<n][1<<n];
			fill(next, oo);
			for(int c0 = 0 ; c0 < 1<<n ; c0++)
				for(int c1 = 0 ; c1 < 1<<n ; c1++)
					if(curr[c0][c1] != oo)
						for(int c2 = 0 ; c2 < (j == m-1 ? 1 : 1<<n) ; c2++)
						{
							int done = 0;
							for(int i = 0 ; i < n ; i++)
								if(((1<<i) & c1) == 0)
								{
									int up = i-1;
									int down = i+1;
									if(up >= 0 && ((1<<up) & c1) != 0)
										done |= 1<<i;
									if(down < n && ((1<<down) & c1) != 0)
										done |= 1<<i;
									if(((1<<i) & c0) != 0)
										done |= 1<<i;
									if(((1<<i) & c2) != 0)
										done |= 1<<i;
								}

							next[c1][c2] = Math.min(next[c1][c2], curr[c0][c1] + n - Integer.bitCount(done));

						}
			curr = next;
		}

		int res = oo;
		for(int i = 0 ; i < 1<<n ; i++)
			for(int j = 0 ; j < 1<<n ; j++)
				res = Math.min(res, curr[i][j]);

		System.out.println(n*m - res);
	}

	private static void fill(int[][] array, int val)
	{
		for(int [] fill : array)
			Arrays.fill(fill, val);
	}
}


import java.util.*;

public class cf112e {
	static int n,m,s;
	static int[][][] memo;
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		n = in.nextInt();
		m = in.nextInt();
		if(n > m) {
			int tmp = n;
			n = m;
			m = tmp;
		}
		s = (1<<n);
		memo = new int[s][s][m];
		for(int i=0; i<s; i++)
			for(int j=0; j<s; j++)
				Arrays.fill(memo[i][j], -1);
		int ret = go(0,0,0);
		System.out.println(n*m - ret);
	}
	static int go(int last, int trans, int r) {
		if(r==m) {
			if(trans == 0) return 0;
			return 100;
		}
		if(memo[last][trans][r] != -1) return memo[last][trans][r];
		int best = 100;
		for(int crnt = 0; crnt < s; crnt++) {
			if((trans & ~crnt) != 0) continue; //certain ones have to be here
			for(int pass = 0; pass < s; pass++) {
				int tmp = ((1<<n)-1) & ~last;	//move back the ones you can
				if((pass & ~tmp) != 0) continue; //certain ones have to move on
				tmp = tmp & ~pass; //find which ones stay
				boolean fail = false;
				for(int k=0; k<n; k++) //make sure that the ones that stay
					if(isSet(tmp,k) && !(isSet(crnt,k-1) || isSet(crnt,k) || isSet(crnt,k+1)))
						fail = true;
				if(fail) continue;
				best = Math.min(best, Integer.bitCount(crnt) + go(crnt,pass,r+1));
			}
		}
		return memo[last][trans][r] = best;
	}
	static boolean isSet(int x, int p) {
		if(p < 0 || p >= n) return false;
		return (x & (1<<p)) != 0;
	}
}


import java.util.Arrays;

/**
 * Created by IntelliJ IDEA.
 * User: piyushd
 * Date: 3/26/11
 * Time: 10:53 PM
 * To change this template use File | Settings | File Templates.
 */
public class TaskC {

    final  int INF = 123456;
    int[][][] memo;
    int N, M;

    int solve(int row, int prevFreeMask, int curStayMask) {
        if(row == N) return (curStayMask == 0) ? 0 : -INF;
        if(memo[row][prevFreeMask][curStayMask] != -1) return memo[row][prevFreeMask][curStayMask];

        int res = 0;
        for(int mask = 0; mask < (1<<M); mask++) {
            if((mask & curStayMask) == curStayMask) {
                int freeCellsMask = (1<<M) - 1 - mask;
                int toMoveMask = freeCellsMask;

                for(int i = 0; i < M; i++) {
                    if((toMoveMask & (1<<i)) > 0) {
                        if(i > 0) {
                            if((mask & (1<<(i - 1))) > 0) {
                                toMoveMask -= (1<<i);
                                continue;
                            }
                        }

                        if(i < M - 1) {
                            if((mask & (1<<(i + 1))) > 0) {
                                toMoveMask -= (1<<i);
                                continue;
                            }
                        }
                    }
                }

                if (row > 0) {
                    for (int prevFillMask = toMoveMask; prevFillMask > 0; prevFillMask = (prevFillMask - 1) & toMoveMask) {
                        int bc1 = Integer.bitCount(freeCellsMask);
                        int bc2 = Integer.bitCount(prevFreeMask & prevFillMask);
                        res = Math.max(res, bc1 - bc2 + solve(row + 1, freeCellsMask, toMoveMask ^ prevFillMask));
                    }
                }

                res = Math.max(res, Integer.bitCount(freeCellsMask) + solve(row + 1, freeCellsMask, toMoveMask));
            }
        }

        return memo[row][prevFreeMask][curStayMask] = res;
    }

    void run() {
        N = nextInt();
        M = nextInt();
        if(M > N) {
            int temp = M;
            M = N;
            N = temp;
        }

        this.memo = new int[N + 1][1<<M][1<<M];
        for(int[][] g : memo) for(int[] f : g) Arrays.fill(f, -1);

        System.out.println(solve(0, 0, 0));
    }

    int nextInt(){
        try{
            int c = System.in.read();
            if(c == -1) return c;
            while(c != '-' && (c < '0' || '9' < c)){
                c = System.in.read();
                if(c == -1) return c;
            }
            if(c == '-') return -nextInt();
            int res = 0;
            do{
                res *= 10;
                res += c - '0';
                c = System.in.read();
            }while('0' <= c && c <= '9');
            return res;
        }catch(Exception e){
            return -1;
        }
    }

    long nextLong(){
        try{
            int c = System.in.read();
            if(c == -1) return -1;
            while(c != '-' && (c < '0' || '9' < c)){
                c = System.in.read();
                if(c == -1) return -1;
            }
            if(c == '-') return -nextLong();
            long res = 0;
            do{
                res *= 10;
                res += c-'0';
                c = System.in.read();
            }while('0' <= c && c <= '9');
            return res;
        }catch(Exception e){
            return -1;
        }
    }

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

    String next(){
        try{
            StringBuilder res = new StringBuilder("");
            int c = System.in.read();
            while(Character.isWhitespace(c))
                c = System.in.read();
            do{
                res.append((char)c);
            }while(!Character.isWhitespace(c=System.in.read()));
            return res.toString();
        }catch(Exception e){
            return null;
        }
    }

    String nextLine(){
        try{
            StringBuilder res = new StringBuilder("");
            int c = System.in.read();
            while(c == '\r' || c == '\n')
                c = System.in.read();
            do{
                res.append((char)c);
                c = System.in.read();
            }while(c != '\r' && c != '\n');
            return res.toString();
        }catch(Exception e){
            return null;
        }
    }

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


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

/**
 * Author -
 * User: kansal
 * Date: 9/3/11
 * Time: 5:28 PM
 */
public class CF85E {
    public static void main(String[] args) {
        reader = new BufferedReader(new InputStreamReader(System.in));

        int height = nextInt(), width = nextInt();
        if (width > height) {
            int t = width;
            width = height;
            height = t;
        }

        final int INF = height * width + 10;
        int[][][] dp = new int[height + 1][1 << width][1 << width];
        for (int[][] ints : dp) {
            for (int[] anInt : ints) {
                Arrays.fill(anInt, INF);
            }
        }

        dp[0][0][0] = 0;
        for(int r = 0; r < height; ++r) {
            for(int uncovered = 0; uncovered < (1 << width); ++uncovered) {
                for(int mask = 0; mask < (1 << width); ++mask) {
                    if (dp[r][uncovered][mask] == INF) {
                        continue;
                    }
                    for(int curMask = uncovered; curMask < (1 << width); curMask = (curMask + 1) | uncovered) {
                        int curUncovered = (1 << width) - 1;
                        for(int i = 0; i < width; ++i) {
                            if (hasBit(mask, i) || hasBit(curMask, i)) {
                                curUncovered &= ~(1 << i);
                            }
                            if (i > 0 && hasBit(curMask, i-1)) {
                                curUncovered &= ~(1 << i);
                            }
                            if (i < width-1 && hasBit(curMask, i+1)) {
                                curUncovered &= ~(1 << i);
                            }
                        }
                        dp[r+1][curUncovered][curMask] = Math.min(dp[r+1][curUncovered][curMask], dp[r][uncovered][mask] + Integer.bitCount(curMask));
                    }
                }
            }
        }

        int res = INF;
        for(int x: dp[height][0]) res = Math.min(res, x);

        System.out.println(height * width - res);
    }

    private static boolean hasBit(int mask, int bit) {
        return (((mask >> bit) & 1) == 1);
    }

    public static BufferedReader reader;

    public static StringTokenizer tokenizer = null;

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

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

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

    static public String next() {
        return nextToken();
    }

    static public String nextLine() {
        try {
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}


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

/**
 * Author -
 * User: kansal
 * Date: 9/3/11
 * Time: 5:28 PM
 */
public class CF85C {
    public static void main(String[] args) {
        reader = new BufferedReader(new InputStreamReader(System.in));

        int height = nextInt(), width = nextInt();
        if (width > height) {
            int t = width;
            width = height;
            height = t;
        }

        final int INF = height * width + 10;
        final int ALL_BITS = (1 << width)  - 1;
        int[][][] dp = new int[height + 1][1 << width][1 << width];
        for (int[][] ints : dp) {
            for (int[] anInt : ints) {
                Arrays.fill(anInt, INF);
            }
        }

        dp[0][0][0] = 0;
        for(int r = 0; r < height; ++r) {
            for(int uncovered = 0; uncovered < (1 << width); ++uncovered) {
                for(int mask = 0; mask < (1 << width); ++mask) {
                    if (dp[r][uncovered][mask] == INF) {
                        continue;
                    }
                    for(int curMask = uncovered; curMask < (1 << width); curMask = (curMask + 1) | uncovered) {
                        int curCovered = (mask | curMask);
                        curCovered |= (curMask >> 1);
                        curCovered |= (ALL_BITS & (curMask << 1));

                        int curUncovered = ALL_BITS ^ curCovered;
                        dp[r+1][curUncovered][curMask] = Math.min(dp[r+1][curUncovered][curMask], dp[r][uncovered][mask] + Integer.bitCount(curMask));
                    }
                }
            }
        }

        int res = INF;
        for(int x: dp[height][0]) res = Math.min(res, x);

        System.out.println(height * width - res);
    }

    private static boolean hasBit(int mask, int bit) {
        return (((mask >> bit) & 1) == 1);
    }

    public static BufferedReader reader;

    public static StringTokenizer tokenizer = null;

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

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

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

    static public String next() {
        return nextToken();
    }

    static public String nextLine() {
        try {
            return reader.readLine();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

}


/*
If you want to aim high, aim high
Don't let that studying and grades consume you
Just live life young
******************************
If I'm the sun, you're the moon
Because when I go up, you go down
*******************************
I'm working for the day I will surpass you
https://www.a2oj.com/Ladder16.html
*/
import java.util.*;
import java.io.*;
import java.math.*;

   public class x111C
   {
      public static void main(String omkar[]) throws Exception
      {
         BufferedReader infile = new BufferedReader(new InputStreamReader(System.in));
         StringTokenizer st = new StringTokenizer(infile.readLine());
         int R = Integer.parseInt(st.nextToken());
         int C = Integer.parseInt(st.nextToken());
         if(R > C)
         {
            int t = R;
            R = C;
            C = t;
         }
         //dp[c][m1][m2] = min spoders in first c columns
         int[][][] dp = new int[C+1][1 << R][1 << R];
         for(int i=0; i <= C; i++)
            for(int mask=0; mask < (1<<R); mask++)
               Arrays.fill(dp[i][mask], 69);
         for(int mask=0; mask < (1<<R); mask++)
            dp[0][0][mask] = 0;
         for(int c=1; c <= C; c++)
            for(int mask1=0; mask1 < (1<<R); mask1++)
               for(int mask2=0; mask2 < (1<<R); mask2++)
                  for(int mask3=0; mask3 < (1<<R); mask3++)
                  {
                     boolean works = true;
                     for(int b=0; b < R; b++)
                        if((mask2&(1<<b)) == 0)
                        {
                           if(b > 0 && (mask2&(1<<(b-1))) > 0);
                           else if(b+1 < R && (mask2&(1<<(b+1))) > 0);
                           else if((mask1&(1<<b)) > 0);
                           else if((mask3&(1<<b)) > 0);
                           else  works = false;
                        }
                     if(works)
                        dp[c][mask2][mask3] = Math.min(dp[c][mask2][mask3], dp[c-1][mask1][mask2]+Integer.bitCount(mask1));
                  }
         int res = 0;
         for(int mask=0; mask < (1<<R); mask++)
            res = Math.max(res, R*C-(dp[C][mask][0]+Integer.bitCount(mask)));
         System.out.println(res);
      }
   }


import java.util.*;

public class E
{
   public static void main(String[] args)
   {
      new E(new Scanner(System.in));
   }

   int N, M;
   int[][][] memo;

   int go(int i, int j, int mask)
   {
      if (i == N)
         return go(0, j+1, mask);
      if (j == M)
      {
         int mm = mask%(1<<N);
         //System.out.println(Integer.toBinaryString(mm)+" "+Integer.toBinaryString(mask));
         if (mm != ((1<<N)-1))
            return N*M;
         return 0;
      }

      if (memo[i][j][mask] != -1)
         return memo[i][j][mask];

      // Now place a spider cluster here
      int nMask = mask;
      int prevMask = 0;
      if (i > 0)
         prevMask = 1 << (N-1);
      int nextMask = 0;
      if (i < (N-1))
         nextMask = 1 << (N+1);
      int curMask = 1 << N;
      int nextRowMask = 1 << (N+N);
      nMask = nMask|prevMask|nextMask|curMask|nextRowMask;
      nMask = nMask/2;
      int res = 1+go(i+1, j, nMask);

      int pr = mask%2;
      if (pr == 1)
      {
         // We have the option to skip here
         int rr = go(i+1, j, mask/2);
         if (rr < res)
            res = rr;
      }

      //System.out.printf("%d %d %s = %d%n", i, j, Integer.toBinaryString(mask), res);
      memo[i][j][mask] = res;
      return res;
   }

   public E(Scanner in)
   {
      int[] vals = new int[2];
      vals[0] = in.nextInt();
      vals[1] = in.nextInt();
      Arrays.sort(vals);

      N = vals[0];
      M = vals[1];
      memo = new int[N][M][1<<(N+N+1)];
      fill3(memo, -1);

      int r1 = go(0, 0, (1<<N)-1);
      int res = N*M-r1;
      System.out.printf("%d%n", res);

   }

   void fill3(int[][][] vvv, int val)
   {
      for (int[][] vv : vvv)
         for (int[] v : vv)
            Arrays.fill(v, val);
   }
}


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

public class E
{
    private static final int oo = 1000000000;
    public static void main(String[] args) throws Exception
    {
        Scanner in = new Scanner(System.in);
        int n = in.nextInt();
        int m = in.nextInt();
        if(n > m)
        {
            int t = n;
            n = m;
            m = t;
        }

        int [][] curr = new int[1<<n][1<<n];
        fill(curr, oo);
        Arrays.fill(curr[0], 0);

        for(int j = 0 ; j < m ; j++)
        {
            int [][] next = new int[1<<n][1<<n];
            fill(next, oo);
            for(int c0 = 0 ; c0 < 1<<n ; c0++)
                for(int c1 = 0 ; c1 < 1<<n ; c1++)
                    if(curr[c0][c1] != oo)
                        for(int c2 = 0 ; c2 < (j == m-1 ? 1 : 1<<n) ; c2++)
                        {
                            int all = (1<<n) - 1;
                            int done = (all&(c1>>1)) | (all&(c1<<1)) | c0 | c2;
                            done &= (all^c1);

                            next[c1][c2] = Math.min(next[c1][c2], curr[c0][c1] + n - Integer.bitCount(done));

                        }
            curr = next;
        }

        int res = oo;
        for(int i = 0 ; i < 1<<n ; i++)
            for(int j = 0 ; j < 1<<n ; j++)
                res = Math.min(res, curr[i][j]);

        System.out.println(n*m - res);
    }

    private static void fill(int[][] array, int val)
    {
        for(int [] fill : array)
            Arrays.fill(fill, val);
    }
}


/*(c) gorlum0 [at] gmail.com*/
import java.io.*;
import java.util.*;
import java.math.*;

public class E
{
    int bitcount(int x) {
        int c = 0;
        for ( ; x != 0; c++)
            x &= x-1;
        return c;
    }

    boolean bit(int x, int i)
    {
        if (i < 0) return false;
        return (x>>i & 1) == 1 ? true : false;
    }

    int solve(int n, int m)
    {
        if (m > n) { int x = m; m = n; n = x; }
        int maxmask = 1<<m;

        int[][][] dp = new int[n+1][maxmask][maxmask];
        for (int i = 0; i <= n; i++)
            for (int j = 0; j < maxmask; j++)
                for (int k = 0; k < maxmask; k++)
                    dp[i][j][k] = inf;
        for (int i = 0; i < maxmask; i++)
            dp[0][0][i] = bitcount(i);

        for (int i = 1; i <= n; i++)
            for (int b = 0; b < maxmask; b++)
                for (int c = 0; c < maxmask; c++)
                    for (int a = 0; a < maxmask; a++) {
                        boolean nospider = false;
                        for (int j = 0; j < m; j++)
                            if (not(bit(a,j) || bit(c,j) || bit(b,j-1) || bit(b,j) || bit(b,j+1))) {
                                nospider = true;
                                break;
                            }
                        if (nospider) continue;

                        dp[i][b][c] = Math.min(dp[i][b][c], dp[i-1][a][b] + bitcount(c));
                    }
        int res = inf;
        for (int b = 0; b < maxmask; b++)
            res = Math.min(res, dp[n][b][0]);
        return n*m - res;
    }

    void main() throws IOException {
        int n;
        while ((n = nextInt()) != EOF) {
            int m = nextInt();
            out.println(solve(n, m));
        }
    }

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

    // ======================================================================

    int inf = (int) 1e9;
    final int EOF = -1;

    boolean not(boolean p) { return !p; }

    int sqr(int x) { return x*x; }
    long sqr(long x) { return x*x; }
    double sqr(double x) { return x*x; }

    BufferedReader fin;
    StringTokenizer st;
    PrintWriter out;

    public void run() {
        try {
            fin = new BufferedReader(new InputStreamReader(System.in));
            st = null;
            out = new PrintWriter(System.out);
            main();
            fin.close();
            out.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 (st == null || !st.hasMoreTokens()) {
            String line = fin.readLine();
            if (line == null) return "-1";
            else st = new StringTokenizer(line);
        }
        return st.nextToken();
    }
}


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

import static java.lang.Math.*;

public class Code implements Runnable {

    public static void main(String[] args) throws IOException {
        new Thread(new Code()).start();
    }

    private void solve() throws IOException {
        int n = nextInt(), m = nextInt();
        if(n > m) {
            n ^= m;
            m ^= n;
            n ^= m;
        }

        int[][][] dp = new int[41][64][64];
        for(int i = 0; i < 41; ++i)
            for(int j = 0; j < 64; ++j)
                for(int k = 0; k < 64; ++k) dp[i][j][k] = Integer.MAX_VALUE / 2;

        for(int i = 0; i < 64; ++i) dp[0][0][i] = countBit(i);

        for(int i = 1; i <= m; ++i) {
            for(int cur = 0; cur < 64; ++cur) {
                for(int next = 0; next < 64; ++next) {
                    for(int prev = 0; prev < 64; ++prev) {
                        if(!isBad(prev, cur, next, n)) {
                            dp[i][cur][next] = min(dp[i][cur][next], dp[i - 1][prev][cur] + countBit(next));
                        }
                    }
                }
            }
        }

        int ans = Integer.MAX_VALUE;
        for(int i = 0; i < 64; ++i) ans = min(ans, dp[m][i][0]);

        writer.println(n * m - ans);
    }

    private boolean isBit(int bits, int pos) {
        return pos < 0 ? false : ((bits & (1 << pos)) != 0);
    }

    private boolean isBad(int prev, int cur, int next, int count) {
        for(int i = 0; i < count; ++i)
            if(!(isBit(cur, i - 1) || isBit(cur, i) || isBit(cur, i + 1) || isBit(prev, i) || isBit(next, i)))  return true;
        return false;
    }

    private int countBit(int bits) {
        int ans = 0;
        for(int i = 0; i < 6; ++i)  ans += (bits & (1 << i)) > 0 ? 1 : 0;
        return ans;
    }

    private class Pair<E extends Comparable, V extends Comparable> implements Comparable<Pair<E, V>> {
        public Pair(E first, V second) {
            this.first = first;
            this.second = second;
        }

        @Override
        public int compareTo(Pair<E, V> obj) {
            if(first == obj.first)  return second.compareTo(obj.second);
            return first.compareTo(obj.first);
        }

        @Override
        public boolean equals(Object obj) {
            Pair other = (Pair)obj;
            return first.equals(other.first) && second.equals(other.second);
        }

        public E first;
        public V second;
    }

    @Override
    public void run() {
        try {
            if(in.equals(""))   reader = new BufferedReader(new InputStreamReader(System.in));
            else    reader = new BufferedReader(new FileReader(in));

            if(out.equals(""))  writer = new PrintWriter(new OutputStreamWriter(System.out));
            else    writer = new PrintWriter(new FileWriter(out));
            solve();
        } catch(IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                writer.close();
            } catch(IOException e) {
                e.printStackTrace();
            }
        }
    }

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

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

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

    private float nextFloat() throws IOException {
        return Float.parseFloat(nextToken());
    }

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

    private String in = "", out = "";
    private BufferedReader reader;
    private PrintWriter writer;
    private StringTokenizer st;
}


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

public class E {
    static int n;
    static int m;
    static int[][][] DP;
    static int[] dx = { 0, 0, 1, -1 };
    static int[] dy = { 1, -1, 0, 0 };
    static int inf = 1000000;

    public static int get(int x, int current, int last) {
        if (x == n) {
            if (last == 0)
                return 0;
            else
                return -inf;
        }
        if (DP[x][current][last] != -1)
            return DP[x][current][last];
        int max = 0;
        for (int mask = 0; mask < (1 << m); mask++) {
            int tempLast = last;
            int tempCurrent = current;
            int tempNext = (1 << m) - 1;
            for (int i = 0; i < m; i++)
                if ((mask & (1 << i)) != 0) {
                    if (i > 0)
                        tempCurrent &= ~(1 << (i - 1));
                    if (i < m - 1)
                        tempCurrent &= ~(1 << (i + 1));
                    tempNext &= ~(1 << (i));
                    tempLast &= ~(1 << (i));
                }
            if (tempLast != 0)
                continue;
            max = Math.max(
                    max,
                    m - Integer.bitCount(mask)
                            + get(x + 1, tempNext, tempCurrent & ~mask));
        }
        return DP[x][current][last] = max;
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int x = in.nextInt();
        int y = in.nextInt();
        n = Math.max(x, y);
        m = Math.min(x, y);
        DP = new int[n][1 << m][1 << m];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < (1 << m); j++)
                Arrays.fill(DP[i][j], -1);
        System.out.println(get(0, (1 << m) - 1, 0));
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.FilterInputStream;
import java.io.BufferedInputStream;
import java.util.Random;
import java.io.InputStream;

/**
 * @author khokharnikunj8
 */

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

    void solve() {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        ScanReader in = new ScanReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        E2RotateColumnsHardVersion solver = new E2RotateColumnsHardVersion();
        int testCount = in.scanInt();
        for (int i = 1; i <= testCount; i++)
            solver.solve(i, in, out);
        out.close();
    }

    static class E2RotateColumnsHardVersion {
        int[][] dp;
        int[] cur;

        public void solve(int testNumber, ScanReader in, PrintWriter out) {
            int n = in.scanInt();
            int m = in.scanInt();
            int[][] ar = new int[n][m];
            int[][] max = new int[m][2];
            for (int i = 0; i < n; i++)
                for (int j = 0; j < m; j++)
                    ar[i][j] = in.scanInt();
            for (int i = 0; i < m; i++) {
                for (int j = 0; j < n; j++) max[i][0] = Math.max(max[i][0], ar[j][i]);
                max[i][1] = i;
            }
            CodeHash.shuffle(max);
            Arrays.sort(max, (o1, o2) -> -o1[0] + o2[0]);
            dp = new int[2][1 << n];
            cur = new int[1 << n];
            for (int i = 0; i < Math.min(m, n); i++) {
                Arrays.fill(dp[i & 1], 0);
                for (int k = 0; k < n; k++) {
                    System.arraycopy(dp[(i - 1) & 1], 0, cur, 0, 1 << n);
                    for (int l = 0; l < n; l++) {
                        for (int j = 0; j < 1 << n; j++) {
                            if ((j & (1 << l)) == 0) {
                                cur[j ^ (1 << l)] = Math.max(cur[j ^ (1 << l)], cur[j] + ar[(k + l) % n][max[i][1]]);
                            }
                        }
                    }
                    for (int j = 0; j < 1 << n; j++) dp[i & 1][j] = Math.max(dp[i & 1][j], cur[j]);
                }
            }
            out.println(dp[Math.min(n, m) & 1 ^ 1][(1 << n) - 1]);
        }

    }

    static class CodeHash {
        public static void shuffle(int[][] ar) {
            Random rd = new Random(new Random().nextInt());
            for (int i = 0; i < ar.length; i++) {
                int index = rd.nextInt(ar.length);
                int[] temp = ar[i];
                ar[i] = ar[index];
                ar[index] = temp;
            }
        }

    }

    static class ScanReader {
        private byte[] buf = new byte[4 * 1024];
        private int index;
        private BufferedInputStream in;
        private int total;

        public ScanReader(InputStream inputStream) {
            in = new BufferedInputStream(inputStream);
        }

        private int scan() {
            if (index >= total) {
                index = 0;
                try {
                    total = in.read(buf);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (total <= 0) return -1;
            }
            return buf[index++];
        }

        public int scanInt() {
            int integer = 0;
            int n = scan();
            while (isWhiteSpace(n)) n = scan();
            int neg = 1;
            if (n == '-') {
                neg = -1;
                n = scan();
            }
            while (!isWhiteSpace(n)) {
                if (n >= '0' && n <= '9') {
                    integer *= 10;
                    integer += n - '0';
                    n = scan();
                }
            }
            return neg * integer;
        }

        private boolean isWhiteSpace(int n) {
            if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
            else return false;
        }

    }
}


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

/**
 * 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;
        FastScanner in = new FastScanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskE2 solver = new TaskE2();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskE2 {
        public void solve(int testNumber, FastScanner in, PrintWriter out) {
            int numTests = in.nextInt();
            for (int test = 0; test < numTests; test++) {
                int n = in.nextInt();
                int m = in.nextInt();
                int[][] a = new int[n][m];
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < m; j++) {
                        a[i][j] = in.nextInt();
                    }
                }

                int[] maxInColumn = new int[m];
                for (int j = 0; j < m; j++) {
                    for (int i = 0; i < n; i++) {
                        maxInColumn[j] = Math.max(maxInColumn[j], a[i][j]);
                    }
                }
                Integer[] cols = new Integer[m];
                for (int i = 0; i < m; i++) {
                    cols[i] = i;
                }
                Arrays.sort(cols, (u, v) -> -(maxInColumn[u] - maxInColumn[v]));
                if (m > n) {
                    int[][] na = new int[n][n];
                    for (int i = 0; i < n; i++) {
                        for (int j = 0; j < n; j++) {
                            na[i][j] = a[i][cols[j]];
                        }
                    }
                    m = n;
                    a = na;
                }

                int[] buf = new int[n];
                int[][] sums = new int[m][1 << n];
                int[] sumsCur = new int[1 << n];
                for (int j = 0; j < m; j++) {
                    for (int shift = 0; shift < n; shift++) {
                        for (int i = 0; i < n; i++) {
                            buf[i] = a[(i + shift) % n][j];
                        }
                        for (int mask = 0; mask < 1 << n; mask++) {
                            if (mask > 0) {
                                int k = Integer.numberOfTrailingZeros(mask);
                                sumsCur[mask] = sumsCur[mask ^ (1 << k)] + buf[k];
                                sums[j][mask] = Math.max(sums[j][mask], sumsCur[mask]);
                            }
                        }
                    }
                }

                int[] d = new int[1 << n];
                int[] nd = new int[1 << n];
                for (int j = 0; j < m; j++) {
                    System.arraycopy(d, 0, nd, 0, d.length);
                    for (int mask = 0; mask < 1 << n; mask++) {
                        for (int submask = mask; submask > 0; submask = (submask - 1) & mask) {
                            nd[mask] = Math.max(nd[mask], d[mask ^ submask] + sums[j][submask]);
                        }
                    }
                    int[] t = d;
                    d = nd;
                    nd = t;
                }
                int ans = 0;
                for (int x : d) {
                    ans = Math.max(ans, x);
                }
                out.println(ans);
            }
        }

    }

    static class FastScanner {
        private BufferedReader in;
        private StringTokenizer st;

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

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

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

    }
}


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

/**
 * 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;
        InputReader in = new InputReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskE2 solver = new TaskE2();
        int testCount = Integer.parseInt(in.next());
        for (int i = 1; i <= testCount; i++)
            solver.solve(i, in, out);
        out.close();
    }

    static class TaskE2 {
        public void solve(int testNumber, InputReader in, PrintWriter out) {
            int n = in.nextInt();
            int m = in.nextInt();
            TaskE2.Column[] columns = new TaskE2.Column[m];
            for (int i = 0; i < m; ++i) columns[i] = new TaskE2.Column(new int[n]);
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < m; ++j) {
                    columns[j].vals[i] = in.nextInt();
                }
            }
            for (int i = 0; i < m; ++i) columns[i].initMax();
            Arrays.sort(columns, new Comparator<TaskE2.Column>() {

                public int compare(TaskE2.Column o1, TaskE2.Column o2) {
                    return o2.max - o1.max;
                }
            });
            if (columns.length > n) {
                columns = Arrays.copyOf(columns, n);
            }
            out.println(solveOne(columns));
        }

        private int solveOne(TaskE2.Column[] columns) {
            int n = columns[0].vals.length;
            int[] best = new int[1 << n];
            int[] next = new int[1 << n];
            int[] tmp = new int[1 << n];
            for (TaskE2.Column c : columns) {
                System.arraycopy(best, 0, next, 0, best.length);
                for (int rot = 0; rot < n; ++rot) {
                    System.arraycopy(best, 0, tmp, 0, best.length);
                    for (int i = 0, pos = rot; i < n; ++i, ++pos) {
                        if (pos >= n) pos = 0;
                        int val = c.vals[pos];
                        for (int j = 0; j < tmp.length; ++j)
                            if ((j & (1 << i)) == 0) {
                                tmp[j ^ (1 << i)] = Math.max(tmp[j ^ (1 << i)], tmp[j] + val);
                            }
                    }
                    for (int j = 0; j < tmp.length; ++j) {
                        next[j] = Math.max(next[j], tmp[j]);
                    }
                }
                int[] aa = best;
                best = next;
                next = aa;
            }
            return best[best.length - 1];
        }

        static class Column {
            int[] vals;
            int max;

            public Column(int[] vals) {
                this.vals = vals;
            }

            void initMax() {
                max = 0;
                for (int x : vals) max = Math.max(max, x);
            }

        }

    }

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

        public InputReader(InputStream stream) {
            reader = new BufferedReader(new InputStreamReader(stream), 32768);
            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.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Comparator;
import java.util.Collections;
import java.io.InputStream;

/**
 * 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;
        FastScanner in = new FastScanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        TaskE2 solver = new TaskE2();
        solver.solve(1, in, out);
        out.close();
    }

    static class TaskE2 {
        public void solve(int testNumber, FastScanner in, PrintWriter out) {
            int t = in.nextInt();
            while (t-- > 0) {
                int n = in.nextInt(), m = in.nextInt();
                int[][] a = new int[n][m];
                for (int i = 0; i < n; i++) {
                    for (int j = 0; j < m; j++) {
                        a[i][j] = in.nextInt();
                    }
                }
                boolean[][] interesting = new boolean[n][m];
                boolean[] hasInteresting = new boolean[m];
                for (int i = 0; i < n; i++) {
                    List<Pair> list = new ArrayList<>();
                    for (int j = 0; j < m; j++) {
                        list.add(new Pair(a[i][j], j));
                    }
                    Collections.sort(list, Comparator.comparing(pair -> -pair.val));
                    for (int j = 0; j < m && j <= n; j++) {
                        interesting[i][list.get(j).pos] = true;
                        hasInteresting[list.get(j).pos] = true;
                    }
                }
                boolean[] goodMask = new boolean[1 << n];
                for (int mask = 0; mask < 1 << n; mask++) {
                    int best = Integer.MAX_VALUE;
                    int cur = mask;
                    do {
                        best = Math.min(best, cur);
                        cur = (cur >> 1) | ((cur & 1) << (n - 1));
                    } while (cur != mask);
                    goodMask[mask] = (mask == best);
                }
                int[] dp = new int[1 << n];
                for (int i = 0; i < m; i++) {
                    if (!hasInteresting[i]) {
                        continue;
                    }
                    for (int j = 0; j < n; j++) {
                        if (!interesting[j][i]) {
                            continue;
                        }
                        int supermask = (1 << n) - 1 - (1 << j);
                        int val = a[j][i];
                        for (int mask = supermask; ; mask = (mask - 1) & supermask) {
                            if (dp[mask] + val > dp[mask | (1 << j)]) {
                                dp[mask | (1 << j)] = dp[mask] + val;
                            }
                            if (mask == 0) {
                                break;
                            }
                        }
                    }
                    for (int mask = 0; mask < 1 << n; mask++) {
                        if (!goodMask[mask]) {
                            continue;
                        }
                        int best = 0;
                        int cur = mask;
                        do {
                            best = Math.max(best, dp[cur]);
                            cur = (cur >> 1) | ((cur & 1) << (n - 1));
                        } while (cur != mask);
                        do {
                            dp[cur] = best;
                            cur = (cur >> 1) | ((cur & 1) << (n - 1));
                        } while (cur != mask);
                    }
                }
                out.println(dp[(1 << n) - 1]);
            }
        }

        class Pair {
            int val;
            int pos;

            public Pair(int val, int pos) {
                this.val = val;
                this.pos = pos;
            }

        }

    }

    static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

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

        public FastScanner(String fileName) {
            try {
                br = new BufferedReader(new FileReader(fileName));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

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

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

    }
}


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

public class A {
    FastScanner in;
    PrintWriter out;

    void solve() {
        int tc = in.nextInt();
        for (int t = 0; t < tc; t++) {
            int n = in.nextInt();
            int m = in.nextInt();
            O[] a = new O[n * m];
            int[][] cols = new int[m][n + n];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    cols[j][i] = cols[j][i + n] = in.nextInt();
                    a[i * m + j] = new O(i, j, cols[j][i]);
                }
            }
            Arrays.sort(a);
            boolean[] used = new boolean[m];
            int cntUsed = 0;
            for (O o : a) {
                if (!used[o.y]) {
                    used[o.y] = true;
                    cntUsed++;
                    if (cntUsed == n) {
                        break;
                    }
                }
            }
            int[] dp = new int[1 << n];
            int[] ndp = new int[1 << n];
            int[] maxndp = new int[1 << n];
            for (int col = 0; col < m; col++) {
                if (!used[col]) {
                    continue;
                }
                int[] curColumn = cols[col];
                for (int shift = 0; shift < n; shift++) {
                    System.arraycopy(dp, 0, ndp, 0, ndp.length);
                    for (int mask = 0; mask < 1 << n; mask++) {
                        for (int bit = 0; bit < n; bit++) {
                            if (((1 << bit) & mask) == 0) {
                                int nmask = mask | (1 << bit);
                                ndp[nmask] = Math.max(ndp[nmask], ndp[mask] + curColumn[bit + shift]);
                            }
                        }
                    }
                    for (int i = 0; i < ndp.length; i++) {
                        maxndp[i] = Math.max(maxndp[i], ndp[i]);
                    }
                }
                int[] tmp = dp;
                dp = maxndp;
                maxndp = tmp;
            }
            out.println(dp[dp.length - 1]);
        }
    }

    class O implements Comparable<O> {
        int x, y, value;

        public O(int x, int y, int value) {
            this.x = x;
            this.y = y;
            this.value = value;
        }

        @Override
        public int compareTo(O o) {
            return -Integer.compare(value, o.value);
        }
    }

    void run() {
        try {
            in = new FastScanner(new File("A.in"));
            out = new PrintWriter(new File("A.out"));

            solve();

            out.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    void runIO() {

        in = new FastScanner(System.in);
        out = new PrintWriter(System.out);

        solve();

        out.close();
    }

    class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        public FastScanner(File f) {
            try {
                br = new BufferedReader(new FileReader(f));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

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

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

        boolean hasMoreTokens() {
            while (st == null || !st.hasMoreTokens()) {
                String s = null;
                try {
                    s = br.readLine();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                if (s == null)
                    return false;
                st = new StringTokenizer(s);
            }
            return true;
        }

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

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

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

    public static void main(String[] args) {
        new A().runIO();
    }
}


import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.Arrays;

public class CF1209E2 {
    public static void main(String[] args) throws Exception {
        boolean local = System.getSecurityManager() == null;
        boolean async = false;

        Charset charset = Charset.forName("ascii");

        FastIO io = local ? new FastIO(new FileInputStream("D:\\DATABASE\\TESTCASE\\Code.in"), System.out, charset) : new FastIO(System.in, System.out, charset);
        Task task = new Task(io, new Debug(local));

        if (async) {
            Thread t = new Thread(null, task, "dalt", 1 << 27);
            t.setPriority(Thread.MAX_PRIORITY);
            t.start();
            t.join();
        } else {
            task.run();
        }

        if (local) {
            io.cache.append("\n\n--memory -- \n" + ((Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory()) >> 20) + "M");
        }

        io.flush();
    }

    public static class Task implements Runnable {
        final FastIO io;
        final Debug debug;
        int inf = (int) 1e8;

        public Task(FastIO io, Debug debug) {
            this.io = io;
            this.debug = debug;
        }

        @Override
        public void run() {
            int t = io.readInt();
            while (t-- > 0)
                solve();
        }

        int[][] prefix = new int[12][1 << 12];
        int[][] profits = new int[12][1 << 12];
        Col[] cols = new Col[2000];

        {
            for (int i = 0; i < 2000; i++) {
                cols[i] = new Col(12);
            }
        }

        public void solve() {
            int n = io.readInt();
            int m = io.readInt();

            for (int i = 0; i < m; i++) {
                cols[i].max = 0;
                cols[i].n = n;
            }
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    cols[j].data[i] = io.readInt();
                    cols[j].max = Math.max(cols[j].max, cols[j].data[i]);
                }
            }

            Arrays.sort(cols, 0, m, (a, b) -> -(a.max - b.max));
            Col[] cols = Arrays.copyOf(this.cols, Math.min(m, n));

            int mask = (1 << n) - 1;
            SubsetGenerator sg = new SubsetGenerator();
            SubsetGenerator2 sg2 = new SubsetGenerator2();
            BitOperator bo = new BitOperator();
            for (int i = 0; i < cols.length; i++) {
                Arrays.fill(profits[i], 0);
                for (int j = 0; j < n; j++) {
                    cols[i].rotate();
                    for (int k = 0; k < n; k++) {
                        sg2.values[k] = cols[i].data[k];
                    }
                    sg2.setSet(mask);
                    while (sg2.hasNext()) {
                        profits[i][sg2.next] = Math.max(profits[i][sg2.next], sg2.val);
                        sg2.next();
                    }
                }
            }

            prefix[0] = profits[0];
            for (int i = 1; i < cols.length; i++) {
                for (int j = 0; j <= mask; j++) {
                    sg.setSet(j);
                    prefix[i][j] = prefix[i - 1][j];
                    while (sg.hasNext()) {
                        int next = sg.next();
                        prefix[i][j] = Math.max(prefix[i][j],
                                profits[i][next] + prefix[i - 1][j ^ next]);
                    }
                }
            }

            io.cache.append(prefix[cols.length - 1][mask]).append('\n');
        }
    }

    /**
     * Bit operations
     */
    public static class BitOperator {
        public int bitAt(int x, int i) {
            return (x >> i) & 1;
        }

        public int bitAt(long x, int i) {
            return (int) ((x >> i) & 1);
        }

        public int setBit(int x, int i, boolean v) {
            if (v) {
                x |= 1 << i;
            } else {
                x &= ~(1 << i);
            }
            return x;
        }

        public long setBit(long x, int i, boolean v) {
            if (v) {
                x |= 1L << i;
            } else {
                x &= ~(1L << i);
            }
            return x;
        }

        /**
         * Determine whether x is subset of y
         */
        public boolean subset(long x, long y) {
            return intersect(x, y) == x;
        }

        /**
         * Merge two set
         */
        public long merge(long x, long y) {
            return x | y;
        }

        public long intersect(long x, long y) {
            return x & y;
        }

        public long differ(long x, long y) {
            return x - intersect(x, y);
        }
    }

    public static class SubsetGenerator2 {
        private int[] meanings = new int[33];
        private int[] values = new int[33];
        private int[] bits = new int[33];
        private int remain;
        private int next;
        private int val;

        public void setSet(int set) {
            int bitCount = 0;
            while (set != 0) {
                meanings[bitCount] = set & -set;
                bits[bitCount] = 0;
                set -= meanings[bitCount];
                bitCount++;
            }
            remain = 1 << bitCount;
            next = 0;
            val = 0;
        }

        public boolean hasNext() {
            return remain > 0;
        }

        private void consume() {
            remain = remain - 1;
            int i;
            for (i = 0; bits[i] == 1; i++) {
                bits[i] = 0;
                next -= meanings[i];
                val -= values[i];
            }
            bits[i] = 1;
            next += meanings[i];
            val += values[i];
        }

        public int next() {
            int returned = next;
            consume();
            return returned;
        }
    }

    public static class SubsetGenerator {
        private int[] meanings = new int[33];
        private int[] bits = new int[33];
        private int remain;
        private int next;

        public void setSet(int set) {
            int bitCount = 0;
            while (set != 0) {
                meanings[bitCount] = set & -set;
                bits[bitCount] = 0;
                set -= meanings[bitCount];
                bitCount++;
            }
            remain = 1 << bitCount;
            next = 0;
        }

        public boolean hasNext() {
            return remain > 0;
        }

        private void consume() {
            remain = remain - 1;
            int i;
            for (i = 0; bits[i] == 1; i++) {
                bits[i] = 0;
                next -= meanings[i];
            }
            bits[i] = 1;
            next += meanings[i];
        }

        public int next() {
            int returned = next;
            consume();
            return returned;
        }
    }

    public static class Col {
        int[] data;
        int max;
        int n;

        public void rotate() {
            int end = data[n - 1];
            System.arraycopy(data, 0, data, 1, n - 1);
            data[0] = end;
        }

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

    public static class FastIO {
        public final StringBuilder cache = new StringBuilder(1 << 13);
        private final InputStream is;
        private final OutputStream os;
        private final Charset charset;
        private StringBuilder defaultStringBuf = new StringBuilder(1 << 13);
        private byte[] buf = new byte[1 << 13];
        private int bufLen;
        private int bufOffset;
        private int next;

        public FastIO(InputStream is, OutputStream os, Charset charset) {
            this.is = is;
            this.os = os;
            this.charset = charset;
        }

        public FastIO(InputStream is, OutputStream os) {
            this(is, os, Charset.forName("ascii"));
        }

        private int read() {
            while (bufLen == bufOffset) {
                bufOffset = 0;
                try {
                    bufLen = is.read(buf);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                if (bufLen == -1) {
                    return -1;
                }
            }
            return buf[bufOffset++];
        }

        public void skipBlank() {
            while (next >= 0 && next <= 32) {
                next = read();
            }
        }

        public int readInt() {
            int sign = 1;

            skipBlank();
            if (next == '+' || next == '-') {
                sign = next == '+' ? 1 : -1;
                next = read();
            }

            int val = 0;
            if (sign == 1) {
                while (next >= '0' && next <= '9') {
                    val = val * 10 + next - '0';
                    next = read();
                }
            } else {
                while (next >= '0' && next <= '9') {
                    val = val * 10 - next + '0';
                    next = read();
                }
            }

            return val;
        }

        public long readLong() {
            int sign = 1;

            skipBlank();
            if (next == '+' || next == '-') {
                sign = next == '+' ? 1 : -1;
                next = read();
            }

            long val = 0;
            if (sign == 1) {
                while (next >= '0' && next <= '9') {
                    val = val * 10 + next - '0';
                    next = read();
                }
            } else {
                while (next >= '0' && next <= '9') {
                    val = val * 10 - next + '0';
                    next = read();
                }
            }

            return val;
        }

        public double readDouble() {
            boolean sign = true;
            skipBlank();
            if (next == '+' || next == '-') {
                sign = next == '+';
                next = read();
            }

            long val = 0;
            while (next >= '0' && next <= '9') {
                val = val * 10 + next - '0';
                next = read();
            }
            if (next != '.') {
                return sign ? val : -val;
            }
            next = read();
            long radix = 1;
            long point = 0;
            while (next >= '0' && next <= '9') {
                point = point * 10 + next - '0';
                radix = radix * 10;
                next = read();
            }
            double result = val + (double) point / radix;
            return sign ? result : -result;
        }

        public String readString(StringBuilder builder) {
            skipBlank();

            while (next > 32) {
                builder.append((char) next);
                next = read();
            }

            return builder.toString();
        }

        public String readString() {
            defaultStringBuf.setLength(0);
            return readString(defaultStringBuf);
        }

        public int readLine(char[] data, int offset) {
            int originalOffset = offset;
            while (next != -1 && next != '\n') {
                data[offset++] = (char) next;
                next = read();
            }
            return offset - originalOffset;
        }

        public int readString(char[] data, int offset) {
            skipBlank();

            int originalOffset = offset;
            while (next > 32) {
                data[offset++] = (char) next;
                next = read();
            }

            return offset - originalOffset;
        }

        public int readString(byte[] data, int offset) {
            skipBlank();

            int originalOffset = offset;
            while (next > 32) {
                data[offset++] = (byte) next;
                next = read();
            }

            return offset - originalOffset;
        }

        public char readChar() {
            skipBlank();
            char c = (char) next;
            next = read();
            return c;
        }

        public void flush() throws IOException {
            os.write(cache.toString().getBytes(charset));
            os.flush();
            cache.setLength(0);
        }

        public boolean hasMore() {
            skipBlank();
            return next != -1;
        }
    }

    public static class Debug {
        private boolean allowDebug;

        public Debug(boolean allowDebug) {
            this.allowDebug = allowDebug;
        }

        public void assertTrue(boolean flag) {
            if (!allowDebug) {
                return;
            }
            if (!flag) {
                fail();
            }
        }

        public void fail() {
            throw new RuntimeException();
        }

        public void assertFalse(boolean flag) {
            if (!allowDebug) {
                return;
            }
            if (flag) {
                fail();
            }
        }

        private void outputName(String name) {
            System.out.print(name + " = ");
        }

        public void debug(String name, int x) {
            if (!allowDebug) {
                return;
            }

            outputName(name);
            System.out.println("" + x);
        }

        public void debug(String name, long x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println("" + x);
        }

        public void debug(String name, double x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println("" + x);
        }

        public void debug(String name, int[] x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println(Arrays.toString(x));
        }

        public void debug(String name, long[] x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println(Arrays.toString(x));
        }

        public void debug(String name, double[] x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println(Arrays.toString(x));
        }

        public void debug(String name, Object x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println("" + x);
        }

        public void debug(String name, Object... x) {
            if (!allowDebug) {
                return;
            }
            outputName(name);
            System.out.println(Arrays.deepToString(x));
        }
    }
}


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

public class E {
    public static void main(String[] args) {
        FastScanner scanner = new FastScanner();
        PrintWriter out = new PrintWriter(System.out, false);
        int t = scanner.nextInt();
        while(t-->0) {
            int n = scanner.nextInt();
            int m = scanner.nextInt();
            Col[] cols = new Col[m];
            int[][] mat = new int[m][n];
            for(int i = 0; i < n; i++) {
                for(int j =0; j < m; j++) {
                    mat[j][i] = scanner.nextInt();
                }
            }
            for(int i = 0; i < m; i++) {
                cols[i] = new Col(mat[i]);
            }
            Arrays.sort(cols);
            int maxMask = 1 << n;
            int[] dp = new int[maxMask];
            Arrays.fill(dp, -1);
            dp[0] = 0;
            int sz = Math.min(n, m);
            int[][] ss = new int[sz][maxMask];
            //2^n * n^3
            for(int i = 0; i < sz; i++) {
                int[] curArr = cols[i].arr.clone();
                for(int j = 0; j < n; j++) {
                    for(int mask = 0; mask < maxMask; mask++) {
                        int cur = 0;
                        for(int k = 0; k < n; k++) if ((( 1 << k) & mask) > 0) cur += curArr[k];
                        ss[i][mask] = Math.max(ss[i][mask], cur);
                    }
                    curArr = shift(curArr);
                }
            }
            for(int i = 0; i < Math.min(n, m); i++) {
                for(int mask = maxMask-1; mask>=0; mask--) {
                    for(int smask = mask; smask >= 0; smask = (smask-1)&mask) {
                        if (dp[smask] == -1) continue;
                        dp[mask] = Math.max(dp[mask], dp[smask] + ss[i][mask ^ smask]);
                        if (smask == 0) break;
                    }
                }
            }
            out.println(dp[maxMask-1]);
        }
        out.flush();
    }
    static int[] shift (int[] a) {
        int[] b = new int[a.length];
        b[0] = a[a.length-1];
        for(int i = 0; i < a.length-1; i++) {
            b[i+1] = a[i];
        }
        return b;
    }
    static class Col implements Comparable<Col> {
        int[] arr;
        int[] sorted;
        public Col(int[] a) {
            arr = a;
            sorted= arr.clone();
            Arrays.sort(sorted);
        }
        public int compareTo(Col col) {
            return -sorted[sorted.length-1] + col.sorted[sorted.length-1];
        }
    }
    public static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        public FastScanner(Reader in) {
            br = new BufferedReader(in);
        }

        public FastScanner() {
            this(new InputStreamReader(System.in));
        }

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

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

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

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

        String readNextLine() {
            String str = "";
            try {
                str = br.readLine();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return str;
        }
    }
}


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

public class E2
{
    PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)));
    BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    // BufferedReader in;
    StringTokenizer tok;

    public void go() throws IOException
    {
        // long start = System.nanoTime();
        // in = new BufferedReader(new FileReader(new File("input.txt")));
        StringTokenizer tok = new StringTokenizer(in.readLine());
        int zzz = Integer.parseInt(tok.nextToken());
        for (int zz = 0; zz < zzz; zz++)
        {
            ntok();
            int n = ipar();
            int m = ipar();
            int[][] mat = new int[m][n+1];
            for (int i = 0; i < n; i++)
            {
                ntok();
                for (int e = 0; e < m; e++)
                {
                    mat[e][i] = ipar();
                }
            }
            for (int i = 0; i < m; i++)
            {
                for (int e = 0; e < n; e++)
                {
                    mat[i][n] = Math.max(mat[i][n], mat[i][e]);
                }
            }
            ArrayList<int[]> list = new ArrayList<>();
            for (int i = 0; i < m; i++)
            {
                list.add(mat[i]);
            }
            Collections.sort(list, (a, b) -> {
                return -Integer.compare(a[n], b[n]);
            });
            for (int i = 0; i < m; i++)
            {
                mat[i] = list.get(i);
            }
            m = Math.min(m, n);

            int[][] dp = new int[1 << n][m+1];
            for (int i = m-1; i >= 0; i--)
            {
                int[] temp = new int[1 << n];
                for (int r = 0; r < n; r++)
                {
                    for (int j = 0; j < 1 << n; j++)
                    {
                        temp[j] = dp[j][i+1];
                    }
                    for (int j = 0; j < n; j++)
                    {
                        int val = mat[i][(j+r)%n];
                        for (int k = 0; k < 1 << n; k++)
                        {
                            if ((k & (1 << j)) == 0)
                            {
                                temp[k | (1 << j)] = Math.max(temp[k | (1 << j)], temp[k] + val);
                            }
                        }
                    }
                    for (int j = 0; j < 1 << n; j++)
                    {
                        dp[j][i] = Math.max(dp[j][i], temp[j]);
                    }
                }
            }
            out.println(dp[(1 << n) - 1][0]);

            // int[][] best = new int[1 << n][m];
            // for (int i = 0; i < 1 << n; i++)
            // {
            //     for (int e = 0; e < m; e++)
            //     {
            //         best[i][e] = best(i, mat, e);
            //     }
            // }
            // int[][] dp = new int[1 << n][m+1];
            // for (int i = 0; i < 1 << n; i++)
            // {
            //     dp[i][m] = -1000000000;
            // }
            // dp[(1 << n) - 1][m] = 0;
            // for (int i = m-1; i >= 0; i--)
            // {
            //     for (int e = 0; e < 1 << n; e++)
            //     {
            //         dp[e][i] = dp[e][i+1];
            //         int opposite = ~e & ((1 << n) - 1);
            //         for (int w = opposite; w != 0; w = (w-1) & opposite)
            //         {
            //             dp[e][i] = Math.max(dp[e][i], best[w][i] + dp[e|w][i+1]);
            //         }
            //     }
            // }
            // out.println(dp[0][0]);
        }
        // out.printf("%.3f%n", (System.nanoTime() - start) / 1000000.0);

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

    public int best(int mask, int[][] mat, int col)
    {
        int max = 0;
        for (int t = 0; t < mat[0].length-1; t++)
        {
            int sum = 0;
            int mk = mask;
            for (int i = 0; i < mat[0].length-1; i++)
            {
                if (mk % 2 == 1)
                {
                    sum += mat[col][(i+t)%(mat[0].length-1)];
                }
                mk /= 2;
            }
            max = Math.max(max, sum);
        }
        return max;
    }

    public void cycle(int[][] mat, int col)
    {
        int temp = mat[col][0];
        for (int i = 0; i < mat[0].length-2; i++)
        {
            mat[col][i] = mat[col][i+1];
        }
        mat[col][mat[0].length-2] = temp;
    }

    public void ntok() throws IOException
    {
        tok = new StringTokenizer(in.readLine());
    }

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

    public int[] iapar(int n)
    {
        int[] arr = new int[n];
        for (int i = 0; i < n; i++)
        {
            arr[i] = ipar();
        }
        return arr;
    }

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

    public long[] lapar(int n)
    {
        long[] arr = new long[n];
        for (int i = 0; i < n; i++)
        {
            arr[i] = lpar();
        }
        return arr;
    }

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

    public String spar()
    {
        return tok.nextToken();
    }

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