import java.util.*;
import java.io.*;
import java.io.File;
import java.util.AbstractSet;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.math.*;
public class Hello {

  //static FastReader in = new FastReader(System.in);
  public static void main(String[] args) {
    //try {
    //  FileInputStream fis = new FileInputStream("data.txt");
    //  System.setIn(fis);
      Scanner in = new Scanner(System.in);
      // Scanner in = new Scanner(new File("input.txt"));
      // PrintWriter out = new PrintWriter("output.txt");

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

      //r * (c - 1);
      //c * (r - 1);

      int shoe = m * n / 2;

      int []dx = {-1, 0, 0, 1};
      int []dy = {0, -1, 1, 0};

      //shoe * (r * (c - 1)+ c * (r - 1)) *

      //List<Edge>[] graph = createGraph(2 * shoe + 2);
      Graph graph = new Graph(2 * shoe + 2);

      int source = 2 * shoe, sink = 2 * shoe + 1;

      for (int i = 0;i < n;++i) {
	for (int j = 0;j < m;++j) {
	  int id = i * m + j;
	  if ((i + j) % 2 == 0) {//left
	    for (int z = 0;z < 4;++z) {
	      int i1 = i + dx[z];
	      int j1 = j + dy[z];
	      if (i1 < 0 || i1 >= n || j1 < 0 || j1 >= m) continue;
	      int id1 = i1 * m + j1;
	      int wt = (a[i][j] == a[i1][j1])?0:1;
	      graph.addFlowWeightedEdge(id, id1, wt, 1);
	    }
	    graph.addFlowWeightedEdge(source, id, 0, 1);
	  } else {//right
	    graph.addFlowWeightedEdge(id, sink, 0, 1);
	  }
	}
      }
      MinCostFlow mcf = new MinCostFlow(graph, source, sink, false);
      Pair<Long, Long> ans = mcf.minCostMaxFlow();
      System.out.println(ans.first);
    //} catch (FileNotFoundException e){
    //  System.out.println("error");
    //}
  }
///////////////////////
}
class Graph {
  public static final int REMOVED_BIT = 0;

  protected int vertexCount;
  protected int edgeCount;

  private int[] firstOutbound;
  private int[] firstInbound;

  private Edge[] edges;
  private int[] nextInbound;
  private int[] nextOutbound;
  private int[] from;
  private int[] to;
  private long[] weight;
  private long[] capacity;
  private int[] reverseEdge;
  private int[] flags;

  public Graph(int vertexCount) {
    this(vertexCount, vertexCount);
  }

  public Graph(int vertexCount, int edgeCapacity) {
    this.vertexCount = vertexCount;
    firstOutbound = new int[vertexCount];
    Arrays.fill(firstOutbound, -1);

    from = new int[edgeCapacity];
    to = new int[edgeCapacity];
    nextOutbound = new int[edgeCapacity];
    flags = new int[edgeCapacity];
  }

  public int addEdge(int fromID, int toID, long weight, long capacity, int reverseEdge) {
    ensureEdgeCapacity(edgeCount + 1);
    if (firstOutbound[fromID] != -1)
      nextOutbound[edgeCount] = firstOutbound[fromID];
    else
      nextOutbound[edgeCount] = -1;
    firstOutbound[fromID] = edgeCount;
    if (firstInbound != null) {
      if (firstInbound[toID] != -1)
	nextInbound[edgeCount] = firstInbound[toID];
      else
	nextInbound[edgeCount] = -1;
      firstInbound[toID] = edgeCount;
    }
    this.from[edgeCount] = fromID;
    this.to[edgeCount] = toID;
    if (capacity != 0) {
      if (this.capacity == null)
	this.capacity = new long[from.length];
      this.capacity[edgeCount] = capacity;
    }
    if (weight != 0) {
      if (this.weight == null)
	this.weight = new long[from.length];
      this.weight[edgeCount] = weight;
    }
    if (reverseEdge != -1) {
      if (this.reverseEdge == null) {
	this.reverseEdge = new int[from.length];
	Arrays.fill(this.reverseEdge, 0, edgeCount, -1);
      }
      this.reverseEdge[edgeCount] = reverseEdge;
    }
    if (edges != null)
      edges[edgeCount] = createEdge(edgeCount);
    return edgeCount++;
  }

  protected final GraphEdge createEdge(int id) {
    return new GraphEdge(id);
  }

  public final int addFlowWeightedEdge(int from, int to, long weight, long capacity) {
    if (capacity == 0) {
      return addEdge(from, to, weight, 0, -1);
    } else {
      int lastEdgeCount = edgeCount;
      addEdge(to, from, -weight, 0, lastEdgeCount + entriesPerEdge());
      return addEdge(from, to, weight, capacity, lastEdgeCount);
    }
  }

  protected int entriesPerEdge() {
    return 1;
  }

  public final int addFlowEdge(int from, int to, long capacity) {
    return addFlowWeightedEdge(from, to, 0, capacity);
  }

  public final int vertexCount() {
    return vertexCount;
  }

  public final int firstOutbound(int vertex) {
    int id = firstOutbound[vertex];
    while (id != -1 && isRemoved(id))
      id = nextOutbound[id];
    return id;
  }

  public final int nextOutbound(int id) {
    id = nextOutbound[id];
    while (id != -1 && isRemoved(id))
      id = nextOutbound[id];
    return id;
  }

  public final int source(int id) {
    return from[id];
  }

  public final int destination(int id) {
    return to[id];
  }

  public final long weight(int id) {
    if (weight == null)
      return 0;
    return weight[id];
  }

  public final long capacity(int id) {
    if (capacity == null)
      return 0;
    return capacity[id];
  }

  public final long flow(int id) {
    if (reverseEdge == null)
      return 0;
    return capacity[reverseEdge[id]];
  }

  public final void pushFlow(int id, long flow) {
    if (flow == 0)
      return;
    if (flow > 0) {
      if (capacity(id) < flow)
	throw new IllegalArgumentException("Not enough capacity");
    } else {
      if (flow(id) < -flow)
	throw new IllegalArgumentException("Not enough capacity");
    }
    capacity[id] -= flow;
    capacity[reverseEdge[id]] += flow;
  }

  public final boolean flag(int id, int bit) {
    return (flags[id] >> bit & 1) != 0;
  }

  public final boolean isRemoved(int id) {
    return flag(id, REMOVED_BIT);
  }

  protected void ensureEdgeCapacity(int size) {
    if (from.length < size) {
      int newSize = Math.max(size, 2 * from.length);
      if (edges != null)
	edges = resize(edges, newSize);
      from = resize(from, newSize);
      to = resize(to, newSize);
      nextOutbound = resize(nextOutbound, newSize);
      if (nextInbound != null)
	nextInbound = resize(nextInbound, newSize);
      if (weight != null)
	weight = resize(weight, newSize);
      if (capacity != null)
	capacity = resize(capacity, newSize);
      if (reverseEdge != null)
	reverseEdge = resize(reverseEdge, newSize);
      flags = resize(flags, newSize);
    }
  }

  protected final int[] resize(int[] array, int size) {
    int[] newArray = new int[size];
    System.arraycopy(array, 0, newArray, 0, array.length);
    return newArray;
  }

  private long[] resize(long[] array, int size) {
    long[] newArray = new long[size];
    System.arraycopy(array, 0, newArray, 0, array.length);
    return newArray;
  }

  private Edge[] resize(Edge[] array, int size) {
    Edge[] newArray = new Edge[size];
    System.arraycopy(array, 0, newArray, 0, array.length);
    return newArray;
  }

  public final boolean isSparse() {
    return vertexCount == 0 || edgeCount * 20 / vertexCount <= vertexCount;
  }

  protected class GraphEdge implements Edge {
    protected int id;

    protected GraphEdge(int id) {
      this.id = id;
    }

  }

}

class MinCostFlow {
  private final Graph graph;
  private final int source;
  private final int destination;
  private final long[] phi;
  private final long[] dijkstraResult;
  private final int[] lastEdge;
  private final Heap heap;
  private final int vertexCount;
  private final int[] visited;
  private int visitIndex;

  public MinCostFlow(Graph graph, int source, int destination, boolean hasNegativeEdges) {
    this.graph = graph;
    this.source = source;
    this.destination = destination;
    vertexCount = graph.vertexCount();
    phi = new long[vertexCount];
    if (hasNegativeEdges)
      fordBellman();
    dijkstraResult = new long[vertexCount];
    lastEdge = new int[vertexCount];
    if (graph.isSparse()) {
      heap = new Heap(vertexCount, new IntComparator() {
	  public int compare(int first, int second) {
	  return IntegerUtils.longCompare(dijkstraResult[first], dijkstraResult[second]);
	  }
	  }, vertexCount);
      visited = null;
    } else {
      heap = null;
      visited = new int[vertexCount];
    }
  }

  private void fordBellman() {
    Arrays.fill(phi, Long.MAX_VALUE);
    phi[source] = 0;
    boolean[] inQueue = new boolean[vertexCount];
    int[] queue = new int[vertexCount + 1];
    queue[0] = source;
    inQueue[source] = true;
    int stepCount = 0;
    int head = 0;
    int end = 1;
    int maxSteps = 2 * vertexCount * vertexCount;
    while (head != end) {
      int vertex = queue[head++];
      if (head == queue.length)
	head = 0;
      inQueue[vertex] = false;
      int edgeID = graph.firstOutbound(vertex);
      while (edgeID != -1) {
	long total = phi[vertex] + graph.weight(edgeID);
	int destination = graph.destination(edgeID);
	if (graph.capacity(edgeID) != 0 && phi[destination] > total) {
	  phi[destination] = total;
	  if (!inQueue[destination]) {
	    queue[end++] = destination;
	    inQueue[destination] = true;
	    if (end == queue.length)
	      end = 0;
	  }
	}
	edgeID = graph.nextOutbound(edgeID);
      }
      if (++stepCount > maxSteps)
	throw new IllegalArgumentException("Graph contains negative cycle");
    }
  }

  class Heap {
    IntList list;
    int shift;
    int[] index;

    public Heap(int vertexCount, IntComparator intComparator, int vertexCount1) {
      list = new IntArrayList(vertexCount);
      index = new int[vertexCount1];
      Arrays.fill(index, -1);
    }

    public void add(int source) {
      index[source] = 0;
      list.add(source);
    }

    public boolean isEmpty() {
      return shift == list.size();
    }

    public int poll() {
      int result = list.get(shift++);
      index[result] = -1;
      return result;
    }

    public int getIndex(int next) {
      return index[next];
    }

    public void shiftUp(int index) {
    }

    public void clear() {
      list = new IntArrayList();
      shift = 0;
      Arrays.fill(index, -1);
    }
  }

  public Pair<Long, Long> minCostMaxFlow() {
    return minCostMaxFlow(Long.MAX_VALUE);
  }

  public Pair<Long, Long> minCostMaxFlow(long maxFlow) {
    long cost = 0;
    long flow = 0;
    while (maxFlow != 0) {
      if (graph.isSparse())
	dijkstraAlgorithm();
      else
	dijkstraAlgorithmFull();
      if (lastEdge[destination] == -1)
	return Pair.makePair(cost, flow);
      for (int i = 0; i < dijkstraResult.length; i++) {
	if (dijkstraResult[i] != Long.MAX_VALUE)
	  phi[i] += dijkstraResult[i];
      }
      int vertex = destination;
      long currentFlow = maxFlow;
      long currentCost = 0;
      while (vertex != source) {
	int edgeID = lastEdge[vertex];
	currentFlow = Math.min(currentFlow, graph.capacity(edgeID));
	currentCost += graph.weight(edgeID);
	vertex = graph.source(edgeID);
      }
      maxFlow -= currentFlow;
      cost += currentCost * currentFlow;
      flow += currentFlow;
      vertex = destination;
      while (vertex != source) {
	int edgeID = lastEdge[vertex];
	graph.pushFlow(edgeID, currentFlow);
	vertex = graph.source(edgeID);
      }
    }
    return Pair.makePair(cost, flow);
  }

  private void dijkstraAlgorithm() {
    Arrays.fill(dijkstraResult, Long.MAX_VALUE);
    Arrays.fill(lastEdge, -1);
    dijkstraResult[source] = 0;
    heap.clear();
    heap.add(source);
    while (!heap.isEmpty()) {
      int current = heap.poll();
      int edgeID = graph.firstOutbound(current);
      while (edgeID != -1) {
	if (graph.capacity(edgeID) != 0) {
	  int next = graph.destination(edgeID);
	  long total = graph.weight(edgeID) - phi[next] + phi[current] + dijkstraResult[current];
	  if (dijkstraResult[next] > total) {
	    dijkstraResult[next] = total;
	    if (heap.getIndex(next) == -1)
	      heap.add(next);
	    else
	      heap.shiftUp(heap.getIndex(next));
	    lastEdge[next] = edgeID;
	  }
	}
	edgeID = graph.nextOutbound(edgeID);
      }
    }
  }

  private void dijkstraAlgorithmFull() {
    visitIndex++;
    Arrays.fill(dijkstraResult, Long.MAX_VALUE);
    lastEdge[destination] = -1;
    dijkstraResult[source] = 0;
    for (int i = 0; i < vertexCount; i++) {
      int index = -1;
      long length = Long.MAX_VALUE;
      for (int j = 0; j < vertexCount; j++) {
	if (visited[j] != visitIndex && dijkstraResult[j] < length) {
	  length = dijkstraResult[j];
	  index = j;
	}
      }
      if (index == -1) {
	return;
      }
      visited[index] = visitIndex;
      int edgeID = graph.firstOutbound(index);
      while (edgeID != -1) {
	if (graph.capacity(edgeID) != 0) {
	  int next = graph.destination(edgeID);
	  if (visited[next] != visitIndex) {
	    long total = graph.weight(edgeID) - phi[next] + phi[index] + length;
	    if (dijkstraResult[next] > total) {
	      dijkstraResult[next] = total;
	      lastEdge[next] = edgeID;
	    }
	  }
	}
	edgeID = graph.nextOutbound(edgeID);
      }
    }
  }
}

class Pair<U, V> implements Comparable<Pair<U, V>> {
  public final U first;
  public final V second;

  public static<U, V> Pair<U, V> makePair(U first, V second) {
    return new Pair<U, V>(first, second);
  }

  private Pair(U first, V second) {
    this.first = first;
    this.second = second;
  }

  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    Pair pair = (Pair) o;

    return !(first != null ? !first.equals(pair.first) : pair.first != null) && !(second != null ? !second.equals(pair.second) : pair.second != null);

  }

  public int hashCode() {
    int result = first != null ? first.hashCode() : 0;
    result = 31 * result + (second != null ? second.hashCode() : 0);
    return result;
  }

  public String toString() {
    return "(" + first + "," + second + ")";
  }

  public int compareTo(Pair<U, V> o) {
    int value = ((Comparable<U>)first).compareTo(o.first);
    if (value != 0)
      return value;
    return ((Comparable<V>)second).compareTo(o.second);
  }
}

abstract class IntCollection {
  public abstract IntIterator iterator();
  public abstract int size();
  public abstract void add(int value);

  public void addAll(IntCollection values) {
    for (IntIterator it = values.iterator(); it.isValid(); it.advance()) {
      add(it.value());
    }
  }

}

interface IntIterator {
  public int value() throws NoSuchElementException;
  /*
   * @throws NoSuchElementException only if iterator already invalid
   */
  public void advance() throws NoSuchElementException;
  public boolean isValid();
}

interface Edge {
}

abstract class IntList extends IntCollection implements Comparable<IntList> {

  public abstract int get(int index);

  public IntIterator iterator() {
    return new IntIterator() {
      private int size = size();
      private int index = 0;

      public int value() throws NoSuchElementException {
	if (!isValid())
	  throw new NoSuchElementException();
	return get(index);
      }

      public void advance() throws NoSuchElementException {
	if (!isValid())
	  throw new NoSuchElementException();
	index++;
      }

      public boolean isValid() {
	return index < size;
      }
    };
  }

  public int hashCode() {
    int hashCode = 1;
    for (IntIterator i = iterator(); i.isValid(); i.advance())
      hashCode = 31 * hashCode + i.value();
    return hashCode;
  }

  public boolean equals(Object obj) {
    if (!(obj instanceof IntList))
      return false;
    IntList list = (IntList)obj;
    if (list.size() != size())
      return false;
    IntIterator i = iterator();
    IntIterator j = list.iterator();
    while (i.isValid()) {
      if (i.value() != j.value())
	return false;
      i.advance();
      j.advance();
    }
    return true;
  }

  public int compareTo(IntList o) {
    IntIterator i = iterator();
    IntIterator j = o.iterator();
    while (true) {
      if (i.isValid()) {
	if (j.isValid()) {
	  if (i.value() != j.value()) {
	    if (i.value() < j.value())
	      return -1;
	    else
	      return 1;
	  }
	} else
	  return 1;
      } else {
	if (j.isValid())
	  return -1;
	else
	  return 0;
      }
      i.advance();
      j.advance();
    }
  }

}

class IntArrayList extends IntList {
  private int[] array;
  private int size;

  public IntArrayList() {
    this(10);
  }

  public IntArrayList(int capacity) {
    array = new int[capacity];
  }

  public IntArrayList(IntList list) {
    this(list.size());
    addAll(list);
  }

  public int get(int index) {
    if (index >= size)
      throw new IndexOutOfBoundsException();
    return array[index];
  }

  public int size() {
    return size;
  }

  public void add(int value) {
    ensureCapacity(size + 1);
    array[size++] = value;
  }

  public void ensureCapacity(int newCapacity) {
    if (newCapacity > array.length) {
      int[] newArray = new int[Math.max(newCapacity, array.length << 1)];
      System.arraycopy(array, 0, newArray, 0, size);
      array = newArray;
    }
  }

}

interface IntComparator {

}

class IntegerUtils {

  public static int longCompare(long a, long b) {
    if (a < b)
      return -1;
    if (a > b)
      return 1;
    return 0;
  }

}


//package abbyycup2013;
import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class ProblemC1 {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(System.out));

    ProblemC1() throws IOException {
//        reader = new BufferedReader(new FileReader("input.txt"));
//        writer = new PrintWriter(new FileWriter("output.txt"));
    }

    int[] readInts() throws IOException {
        String[] strings = reader.readLine().split(" ");
        int[] ints = new int[strings.length];
        for(int i = 0; i < ints.length; i++) {
            ints[i] = Integer.parseInt(strings[i]);
        }
        return ints;
    }

    int[] tt = null;
    int tx = 0;
    int readInt() throws IOException {
        if(tt == null || tx >= tt.length) {
            tt = readInts();
            tx = 0;
        }
        return tt[tx++];
    }

    boolean isBit(int x, int bit) {
        return ((x >> bit) & 1) > 0;
    }

    void solve() throws IOException {
        int n = readInt();
        int m = readInt();
        int[][] table = new int[n][];
        for(int i = 0; i < n; i++) table[i] = readInts();
        int[][][] dp = new int[n + 1][m][1 << m];
        for(int i = 0; i <= n; i++) for(int j = 0; j < m; j++) for(int k = 0; k < 1 << m; k++) dp[i][j][k] = Integer.MAX_VALUE;
        dp[0][0][0] = 0;
        int inf = Integer.MAX_VALUE;
        for(int i = 0; i < n; i++) {
            for(int j = 0; j < m; j++) {
                for(int mask = 0; mask < 1 << m; mask++) {
                    if(dp[i][j][mask] == inf) continue;
                    if(isBit(mask, j)) {
                        int newJ = j + 1;
                        int newI = i;
                        if(newJ == m) {
                            newI++;
                            newJ = 0;
                        }
                        int newMask = mask ^ (1 << j);
                        dp[newI][newJ][newMask] = Math.min(dp[newI][newJ][newMask], dp[i][j][mask]);
                    }
                    else {
                        if(i + 1 < n) {
                            int newJ = j + 1;
                            int newI = i;
                            if(newJ == m) {
                                newI++;
                                newJ = 0;
                            }
                            int newMask = mask ^ (1 << j);
                            dp[newI][newJ][newMask] = Math.min(dp[newI][newJ][newMask], dp[i][j][mask] + (table[i][j] == table[i + 1][j] ? 0 : 1));
                        }
                        if(j + 1 < m && !isBit(mask, j + 1)) {
                            int newJ = j + 2;
                            int newI = i;
                            if(newJ == m) {
                                newI++;
                                newJ = 0;
                            }
                            int newMask = mask;
                            dp[newI][newJ][newMask] = Math.min(dp[newI][newJ][newMask], dp[i][j][mask] + (table[i][j] == table[i][j + 1] ? 0 : 1));
                        }
                    }
                }

            }
        }
        writer.println(dp[n][0][0]);
        writer.flush();
    }

    void multiSolve() throws IOException {
        int n = readInts()[0];
        for(int i = 0; i < n; i++) {
            solve();
        }
        writer.flush();
    }

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


import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.lang.System.exit;
import static java.util.Arrays.fill;

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

public class C {

	static BufferedReader in;
	static PrintWriter out;
	static StringTokenizer tok;

	static final int DI[] = {1, 0, -1, 0};
	static final int DJ[] = {0, 1, 0, -1};

	static class Pos implements Comparable<Pos> {
		final int i, j, dist;

		Pos(int i, int j, int dist) {
			this.i = i;
			this.j = j;
			this.dist = dist;
		}

		public int compareTo(Pos o) {
			return dist - o.dist;
		}
	}

	static void solve() throws Exception {
		int n = nextInt();
		int m = nextInt();
		int id[][] = new int[n][m];
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				id[i][j] = nextInt() - 1;
			}
		}
		int cap[][][] = new int[n][m][4];
		int cost[][][] = new int[n][m][4];
		int imbalance[][] = new int[n][m];
		int totalCost = n * m / 2;
		int totalImbalance = 0;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if (((i ^ j) & 1) != 0) {
					--imbalance[i][j];
					continue;
				}
				++imbalance[i][j];
				for (int d = 0; d < 4; d++) {
					int ii = i + DI[d];
					int jj = j + DJ[d];
					if (ii < 0 || ii >= n || jj < 0 || jj >= m) {
						continue;
					}
					if (id[i][j] == id[ii][jj]) {
						cap[ii][jj][d ^ 2] = 1;
						cost[ii][jj][d ^ 2] = 1;
						cost[i][j][d] = -1;
						--imbalance[i][j];
						++imbalance[ii][jj];
						--totalCost;
					} else {
						cap[i][j][d] = 1;
					}
				}
				totalImbalance += imbalance[i][j];
			}
		}
		int dist[][] = new int[n][m];
		int distAdj[][] = new int[n][m];
		int back[][] = new int[n][m];
		PriorityQueue<Pos> queue = new PriorityQueue<>();
		while (totalImbalance > 0) {
			for (int t[]: dist) {
				fill(t, Integer.MAX_VALUE);
			}
			for (int t[]: back) {
				fill(t, -1);
			}
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < m; j++) {
					if (imbalance[i][j] > 0) {
						dist[i][j] = 0;
						queue.add(new Pos(i, j, 0));
					}
				}
			}
			do {
				Pos pos = queue.remove();
				int i = pos.i;
				int j = pos.j;
				int cdist = pos.dist;
				if (dist[i][j] != cdist) {
					continue;
				}
				for (int d = 0; d < 4; d++) {
					if (cap[i][j][d] == 0) {
						continue;
					}
					int ii = i + DI[d];
					int jj = j + DJ[d];
					int ndist = cdist + cost[i][j][d] + distAdj[ii][jj] - distAdj[i][j];
					if (ndist < cdist) {
						throw new AssertionError();
					}
					if (dist[ii][jj] > ndist) {
						dist[ii][jj] = ndist;
						back[ii][jj] = d ^ 2;
						queue.add(new Pos(ii, jj, ndist));
					}
				}
			} while (!queue.isEmpty());
			int resDist = Integer.MAX_VALUE;
			int resI = -1;
			int resJ = -1;
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < m; j++) {
					if (imbalance[i][j] < 0 && resDist > dist[i][j]) {
						resDist = dist[i][j];
						resI = i;
						resJ = j;
					}
				}
			}
			if (resDist == Integer.MAX_VALUE) {
				throw new AssertionError();
			}
			{
				int i = resI, j = resJ;
				++imbalance[i][j];
				while (true) {
					int d = back[i][j];
					int ii = i + DI[d];
					int jj = j + DJ[d];
					++cap[i][j][d];
					--cap[ii][jj][d ^ 2];
					i = ii;
					j = jj;
					if (imbalance[i][j] > 0) {
						break;
					}
				}
				--imbalance[i][j];
				--totalImbalance;
				totalCost += dist[resI][resJ] - distAdj[resI][resJ] - dist[i][j] + distAdj[i][j];
			}
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < m; j++) {
					if (dist[i][j] != Integer.MAX_VALUE) {
						distAdj[i][j] -= dist[i][j];
					}
				}
			}
		}
		out.print(totalCost);
	}

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

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

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

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

	public static void main(String[] args) {
		try {
			in = new BufferedReader(new InputStreamReader(System.in));
			out = new PrintWriter(new OutputStreamWriter(System.out));
			solve();
			in.close();
			out.close();
		} catch (Throwable e) {
			e.printStackTrace();
			exit(1);
		}
	}
}


import java.util.Scanner;

public class Main {
    private static int m;
    private static int size=1;
    private static int[] que=new int[(80*80+5)*8];   //队列
    //dis 表示最小花费
    private static int[] dis=new int[80*80+5],pr = new int[80*80+5];
    //vis 表示是否访问过
    private static int[] ed=new int[80*80+5],vis = new int[80*80+5];
    private static int[] edge=new int[80*80+5],next = new int[80*80+5];
    //cost 表示单位流量费用  weight 表示剩余容量
    private static int[] cost=new int[80*80+5],weight = new int[80*80+5],pre = new int[80*80+5];

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        m =scan.nextInt();
        int s = n*m+1,t=n*m+2;
        int[][] a = new int[80+5][80+5];
        for (int i=1;i<=n;i++){
            for (int j=1;j<= m;j++){
                a[i][j] = scan.nextInt();
            }
        }
        for (int i=1;i<=n;i++){
            for (int j=1;j<=m;j++){
                if ((i+j)%2!=0){                //i+j为奇数分到s（起点）那一拨
                    addedge2(s,getNo(i,j),1,0);
                }else {                     //i+j为偶数分到t（终点）那一拨
                    addedge2(getNo(i,j),t,1,0);
                }
                if (j<m) {
                    if ((i+j)%2!=0) addedge2(getNo(i,j),getNo(i,j+1),1,isok(a[i][j],a[i][j+1]));
                    else addedge2(getNo(i,j+1),getNo(i,j),1,isok(a[i][j],a[i][j+1]));
                }
                if (i<n) {
                    if ((i+j)%2!=0) addedge2(getNo(i,j),getNo(i+1,j),1,isok(a[i][j],a[i+1][j]));
                    else addedge2(getNo(i+1,j),getNo(i,j),1,isok(a[i][j],a[i+1][j]));
                }
            }
        }

        int out = Costflow(s,t);
        System.out.println(out);
    }

    private static int Costflow(int s, int t) {
        int totcost=0;
        while (SPFA(s,t)) {
            int flow= 0x7f7f7f7f;
            for(int x=t;x!=s;x=pr[x]){
                flow=Math.min(flow,weight[ed[x]]);
            }
            totcost+=flow*dis[t];
            for(int x=t;x!=s;x=pr[x]){
                weight[ed[x]]-=flow;
                weight[ed[x]^1]+=flow;
            }
        }
        return totcost;

    }

    private static boolean SPFA(int s, int t) {
        for (int i=0;i<dis.length;i++){
            dis[i]= 127;
        }
        for (int i=0;i<vis.length;i++){
            vis[i]= 0;
        }

        dis[que[1]=s]=0;vis[s]=1;
        int head=1,tail=1;
        while (head<=tail) {
            int getNow=que[head++];
            for(int p=pre[getNow];p!=0;p=next[p]) {
                int v=edge[p];
                if (weight[p]>0&&dis[getNow]+cost[p]<dis[v]) {  //存在路径，且权值变小。
                    dis[v]=dis[getNow]+cost[p];
                    if (vis[v]==0){ vis[v]=1;que[++tail]=v;}
                    pr[v]=getNow;
                    ed[v]=p;
                }
            }
            vis[getNow]=0;
        }
        return dis[t]<dis[0];

    }

    private static int isok(int i, int j) {   //判断相邻两格是否相同，相同费用为0，不同为1
        if (i!=j){
            return 1;
        }else {
            return 0;
        }
    }

    private static void addedge2(int u, int v, int w, int c) {
        addedge(u,v,w,c);
        addedge(v,u,0,-c);
    }

    private static void addedge(int u, int v, int w, int c) {
        edge[++size]=v;
        weight[size]=w;
        cost[size]=c;
        next[size]=pre[u];
        pre[u]=size;
    }

    private static int getNo(int i, int j) {
        return (i-1)*m+j;
    }
}


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

/**
 * @author pashka
 */
public class Main {

    private void solve() throws IOException {
        int n = nextInt();
        int m = nextInt();
        int[][] a = new int[n][m];
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                a[i][j] = nextInt();
            }
        }
        init(n * m + 2, n * m * 6);
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                int c = i * m + j;
                if ((i + j) % 2 == 0) {
                    addEdge(0, 2 + c, 1, 0);
                } else {
                    addEdge(2 + c, 1, 1, 0);
                }
                if (i < n - 1) {
                    int cost = 0;
                    if (a[i][j] != a[i + 1][j]) cost = 1;
                    addEdge(2 + c, 2 + c + m, 1, cost);
                    addEdge(2 + c + m, 2 + c, 1, cost);
                }
                if (j < m - 1) {
                    int cost = 0;
                    if (a[i][j] != a[i][j + 1]) cost = 1;
                    addEdge(2 + c, 2 + c + 1, 1, cost);
                    addEdge(2 + c + 1, 2 + c, 1, cost);
                }
            }
        }
        long res = minCostMaxFlow(0, 1);
        out.println(res);
    }

    private BufferedReader br;
    private PrintWriter out;
    private StringTokenizer st;

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

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

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

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

    private void run() throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
//        br = new BufferedReader(new FileReader("input.txt"));
        out = new PrintWriter(System.out);
        solve();
        out.close();
    }

    void init(int n, int m) {
        m *= 2;
        this.n = n;
        this.m = m;
        last = 0;
        head = new int[n];
        nx = new int[m];
        dst = new int[m];
        src = new int[m];
        cp = new int[m];
        ct = new long[m];
        fl = new int[m];
        prev = new int[n];
        heap = new int[n];
        heapRev = new int[n];
        d = new long[n];
        bestDist = new long[n];
        for (int i = 0; i < n; i++) {
            heap[i] = i;
            heapRev[i] = i;
        }
        Arrays.fill(head, -1);
    }

    void addEdge(int x, int y, int capacity, int cost) {
        nx[last] = head[x];
        src[last] = x;
        dst[last] = y;
        cp[last] = capacity;
        ct[last] = cost;
        head[x] = last;
        last++;
        nx[last] = head[y];
        src[last] = y;
        dst[last] = x;
        cp[last] = 0;
        ct[last] = -cost;
        head[y] = last;
        last++;
    }

    // Returns the total cost. Max flow value will be in field maxFlow.
    long minCostMaxFlow(int s, int t) {
        for (int i = 0; i < n; i++) {
            d[i] = INF;
        }
        d[s] = 0;
        boolean ok = false;
        while (!ok) {
            ok = true;
            for (int j = 0; j < m; j++)
                if (fl[j] < cp[j]) {
                    int x = src[j];
                    int y = dst[j];
                    if (d[x] < INF) {
                        if (d[y] > d[x] + ct[j]) {
                            d[y] = d[x] + ct[j];
                            ok = false;
                        }
                    }
                }
        }
        long res = 0;
        maxFlow = 0;
        while (true) {
            dijkstra(s);
            if (bestDist[t] == INF) break;
            {
                int i = t;
                int f = Integer.MAX_VALUE;
                while (i != s) {
                    int e = prev[i];
                    f = Math.min(f, cp[e] - fl[e]);
                    i = src[e];
                }
                if (f < 0) throw new RuntimeException();
                maxFlow += f;
                i = t;
                while (i != s) {
                    int e = prev[i];
                    fl[e] += f;
                    fl[e ^ 1] -= f;
                    if (fl[e] > cp[e]) throw new RuntimeException();
                    res += ct[e] * f;
                    i = src[e];
                }
            }
            for (int i = 0; i < n; i++) {
                d[i] += bestDist[i];
            }
        }
        return res;
    }

    private static final long INF = Long.MAX_VALUE;

    int n, m;
    int[] head;
    int[] nx;
    int[] src;
    int[] dst;
    int[] cp;
    long[] ct;
    int[] fl;
    int last;

    long[] d;
    long[] bestDist;
    int[] prev;
    int[] heap;
    int[] heapRev;
    int heapSize;
    long maxFlow;

    private void dijkstra(int start) {
        Arrays.fill(bestDist, INF);
        heapSize = n;
        bestDist[start] = 0;
        siftUp(heapRev[start]);
        for (int i = 0; i < n; i++) {
            int x = removeMin();
            if (bestDist[x] == INF) {
                break;
            }
            int j = head[x];
            while (j >= 0) {
                if (fl[j] < cp[j]) {
                    int y = dst[j];
                    long d = ct[j] + this.d[x] - this.d[y];
                    if (d < 0) {
                        throw new AssertionError("negative edge");
                    }
                    long ll = bestDist[x] + d;
                    if (bestDist[y] > ll) {
                        prev[y] = j;
                        bestDist[y] = ll;
                        siftUp(heapRev[y]);
                    }
                }
                j = nx[j];
            }
        }
    }

    private void siftUp(int i) {
        while (i > 0 && bestDist[heap[(i - 1) / 2]] > bestDist[heap[i]]) {
            swap(i, (i - 1) / 2);
            i = (i - 1) / 2;
        }
    }

    private int removeMin() {
        int res = heap[0];
        swap(0, heapSize - 1);
        heapSize--;
        int i = 0;
        while (2 * i + 1 < heapSize) {
            int j = i;
            if (bestDist[heap[2 * i + 1]] < bestDist[heap[j]]) {
                j = 2 * i + 1;
            }
            if (2 * i + 2 < heapSize && bestDist[heap[2 * i + 2]] < bestDist[heap[j]]) {
                j = 2 * i + 2;
            }
            if (i == j) break;
            swap(i, j);
            i = j;
        }
        return res;
    }

    private void swap(int i, int j) {
        int t = heap[i];
        heap[i] = heap[j];
        heap[j] = t;
        heapRev[heap[i]] = i;
        heapRev[heap[j]] = j;
    }
}


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

public class C {
	BufferedReader reader;
    StringTokenizer tokenizer;
    PrintWriter out;

	public void solve() throws IOException {
		int N = nextInt();
		int M = nextInt();
		int[][] a = new int[N][M];
		for(int i = 0; i < N; i++)
			for(int j = 0; j < M; j++)
				a[i][j] = nextInt();

		int size = N*M + 2;
		int[][] cap = new int[size][size];
		int[][] cost = new int[size][size];
		int src = N*M;
		int dst = N*M+1;

		int[] dx = new int[]{-1,1,0,0};
		int[] dy = new int[]{0,0,-1,1};

		for(int i = 0; i < N; i++){
			for(int j = 0; j < M; j++){
				int index = i*M+j;
				if( (i+j) % 2 == 0){
					cap[src][index] = 1;
					for(int k = 0; k < 4; k++){
						int newx = i + dx[k];
						int newy = j + dy[k];
						if( newx < 0 || newx >= N || newy < 0 || newy >= M) continue;
						int newindex = newx*M + newy;
						cap[index][newindex] = 1;
						cost[index][newindex] = a[i][j] == a[newx][newy]? 0: 1;
//						cost[newindex][index] = -1* cost[index][newindex];
					}
				}
				else{
					cap[index][dst] = 1;
				}
			}
		}

		MinCostMaxFlow flow = new MinCostMaxFlow();
		int result[] = flow.getMaxFlow(cap, cost, src, dst);
		if( result[0] == N*M/2 )
			out.println(result[1]);
		else
			out.println(-1);
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		new C().run();
	}

	public void run() {
        try {
            reader = new BufferedReader(new InputStreamReader(System.in));
            tokenizer = null;
            out = new PrintWriter(System.out);
            solve();
            reader.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 (tokenizer == null || !tokenizer.hasMoreTokens()) {
            tokenizer = new StringTokenizer(reader.readLine());
        }
        return tokenizer.nextToken();
    }

}

//Min cost max flow algorithm using an adjacency matrix.  If you
//want just regular max flow, setting all edge costs to 1 gives
//running time O(|E|^2 |V|).
//
//Running time: O(min(|V|^2 * totflow, |V|^3 * totcost))
//
//INPUT: cap -- a matrix such that cap[i][j] is the capacity of
//          a directed edge from node i to node j
//
//   cost -- a matrix such that cost[i][j] is the (positive)
//           cost of sending one unit of flow along a
//           directed edge from node i to node j
//
//   source -- starting node
//   sink -- ending node
//
//OUTPUT: max flow and min cost; the matrix flow will contain
//    the actual flow values (note that unlike in the MaxFlow
//    code, you don't need to ignore negative flow values -- there
//    shouldn't be any)
//
//To use this, create a MinCostMaxFlow object, and call it like this:
//
//MinCostMaxFlow nf;
//int maxflow = nf.getMaxFlow(cap,cost,source,sink);

class MinCostMaxFlow {
	boolean found[];
	int N, cap[][], flow[][], cost[][], dad[], dist[], pi[];

	static final int INF = Integer.MAX_VALUE / 2 - 1;

	boolean search(int source, int sink) {
		Arrays.fill(found, false);
		Arrays.fill(dist, INF);
		dist[source] = 0;
		while (source != N) {
			int best = N;
			found[source] = true;
			for (int k = 0; k < N; k++) {
				if (found[k]) continue;
				if (flow[k][source] != 0) {
					int val = dist[source] + pi[source] - pi[k] - cost[k][source];
					if (dist[k] > val) {
						dist[k] = val;
						dad[k] = source;
					}
				}
				if (flow[source][k] < cap[source][k]) {
					int val = dist[source] + pi[source] - pi[k] + cost[source][k];
					if (dist[k] > val) {
						dist[k] = val;
						dad[k] = source;
					}
				}
				if (dist[k] < dist[best]) best = k;
			}
			source = best;
		}
		for (int k = 0; k < N; k++)
			pi[k] = Math.min(pi[k] + dist[k], INF);
		return found[sink];
	}

	int[] getMaxFlow(int cap[][], int cost[][], int source, int sink) {
		this.cap = cap;
		this.cost = cost;

		N = cap.length;
		found = new boolean[N];
		flow = new int[N][N];
		dist = new int[N+1];
		dad = new int[N];
		pi = new int[N];

		int totflow = 0, totcost = 0;
		while (search(source, sink)) {
			int amt = INF;
			for (int x = sink; x != source; x = dad[x])
				amt = Math.min(amt, flow[x][dad[x]] != 0 ? flow[x][dad[x]] :cap[dad[x]][x] - flow[dad[x]][x]);
			for (int x = sink; x != source; x = dad[x]) {
				if (flow[x][dad[x]] != 0) {
					flow[x][dad[x]] -= amt;
					totcost -= amt * cost[x][dad[x]];
				} else {
					flow[dad[x]][x] += amt;
					totcost += amt * cost[dad[x]][x];
				}
			}
			totflow += amt;
		}
		return new int[]{ totflow, totcost };
	}
}


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

public class Problem {

    class Rib {
        Vertex to;
        int capacity, weight, flow;
        Rib invert, next;

        Rib(Vertex to, int capacity, int weight) {
            this.to = to;
            this.capacity = capacity;
            this.weight = weight;
            this.flow = 0;
            next = null;
        }
    }

    class Vertex {
        Rib last;
        int phi, dist;
        int flow;
        boolean used;
        Rib way;

        Vertex() {
            last = null;
            phi = dist = 0;
            flow = 0;
            used = false;
            way = null;
        }
    }

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

    int minCostMaxFlow(Vertex[] g) {
        int n = g.length - 2;
        Vertex s, t;
        int cost = 0;

        while (true) {
            s = g[n];
            t = g[n + 1];
            dejkstra(g, s);
            if (t.way == null) break;
            int df = t.flow;
//            out.println("(" + t.dist + "-(" + s.phi + "-" + t.phi + "))*" + t.flow);
            cost += (t.dist - (s.phi - t.phi)) * t.flow;
            while (t.way != null) {
                t.way.flow += df;
                t.way.invert.flow -= df;
                t = t.way.invert.to;
//                outVertex(t, g, g[g.length - 2], g[g.length - 1]);
//                out.print(" ");
            }
//            out.println();
        }

        return cost;
    }

    void outVertex(Vertex v, Vertex[] g, Vertex s, Vertex t) {
        if (v == s) out.print("s"); else
        if (v == t) out.print("t"); else
            for (int i = 0; i < g.length; i++) {
                if (v == g[i]) out.print(i);
            }
        out.flush();
    }
    int goCnt = 0;

    void dejkstra(Vertex[] g, Vertex s) {
        for (Vertex v : g) {
            v.way = null;
            v.phi += v.dist;
            v.dist = g.length * g.length;
            v.used = false;
        }
        s.flow = g.length * g.length;
        class GoInfo {
            int dist;
            Rib rib;
            Vertex vertex;
            int ind;

            GoInfo(int dist, Vertex vertex, Rib rib, int ind) {
                this.dist = dist;
                this.vertex = vertex;
                this.rib = rib;
                this.ind = ind;
            }
        }
        class GoComp implements Comparator<GoInfo> {
            public int compare(GoInfo a, GoInfo b) {
                if (a.dist == b.dist) {
                    return a.ind - b.ind;
                }
                return a.dist - b.dist;
            }
        }
        TreeSet<GoInfo> q = new TreeSet<GoInfo>(new GoComp());
        q.add(new GoInfo(0, s, null, goCnt++));
        while (!q.isEmpty()) {
            GoInfo info = q.first();
            q.remove(info);
            Vertex v = info.vertex;
            if (v.used) continue;

//            out.print(info.dist + " [");
//            if (info.rib != null) outVertex(info.rib.invert.to, g, s, g[g.length - 1]);
//            out.print(" ");
//            outVertex(v, g, s, g[g.length - 1]);
//            out.println("]");
            v.dist = info.dist;
            v.way = info.rib;
            v.used = true;
            if (info.rib != null) v.flow = Math.min(info.rib.invert.to.flow, info.rib.capacity - info.rib.flow);
            for (Rib rib = v.last; rib != null; rib = rib.next) {
                if (rib.flow == rib.capacity) continue;
                q.add(new GoInfo(v.dist + rib.weight + (v.phi - rib.to.phi), rib.to, rib, goCnt++));
            }
        }
//        for (int i = 0; i < g.length; i++) {
//            out.print("d[");
//            outVertex(g[i], g, g[g.length - 2], g[g.length - 1]);
//            out.println("] = dist(" + g[i].dist + ") phi(" + g[i].phi + ")");
//        }
//        out.println("\n");
    }

    void addRib(Vertex from, Vertex to, int capacity, int weight) {
        Rib direct = new Rib(to, capacity, weight);
        Rib invert = new Rib(from, 0, -weight);
        direct.invert = invert;
        invert.invert = direct;
        direct.next = from.last;
        from.last = direct;
        invert.next = to.last;
        to.last = invert;
    }

    boolean inRange(int x, int min, int max) {
        return x >= min && x <= max;
    }

    Vertex[] readAndMakeGraph() {
        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();
            }
        }
        Vertex[] v = new Vertex[n * m + 2];
        for (int i = 0; i < n * m + 2; i++) v[i] = new Vertex();
        Vertex s = v[n * m];
        Vertex t = v[n * m + 1];
        for (int i = 0; i < n; i++) {
            for (int j = i % 2; j < m; j += 2) {
                for (int dx = -1; dx <= 1; dx++) {
                    for (int dy = -1; dy <= 1; dy++) {
                        if (Math.abs(dx) + Math.abs(dy) == 1) {
                            int ni = i + dx;
                            int nj = j + dy;
                            if (inRange(ni, 0, n - 1) && inRange(nj, 0, m - 1)) {
                                addRib(v[i * m + j], v[ni * m + nj], 1, (a[i][j] == a[ni][nj]? 0 : 1));
                            }
                        }
                    }
                }
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if ((i + j) % 2 == 0) {
                    addRib(s, v[i * m + j], 1, 0);
                } else {
                    addRib(v[i * m + j], t, 1, 0);
                }
            }
        }
        v[v.length - 1].last = null;

        return v;
    }

    void solve() {
        out.print(minCostMaxFlow(readAndMakeGraph()));
        out.close();
    }

    public static void main(String[] args) {
        (new Problem()).solve();
    }
}


import java.io.InputStreamReader;
import java.io.IOException;
import java.util.Arrays;
import java.io.BufferedReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
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);
		TaskC1 solver = new TaskC1();
		solver.solve(1, in, out);
		out.close();
	}
}

class TaskC1 {
    public void solve(int testNumber, InputReader in, PrintWriter out) {
        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 nv = n * m + 2;
        int s = nv - 2;
        int t = nv - 1;
        int[][] cap = new int[nv][nv];
        int[][] cost = new int[nv][nv];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {
                if ((i + j) % 2 == 0) {
                    cap[s][i * m + j] = 1;
                    for (int di = -1; di <= 1; ++di)
                        for (int dj = -1; dj <= 1; ++dj)
                            if (Math.abs(di) + Math.abs(dj) == 1) {
                                int ni = i + di;
                                int nj = j + dj;
                                if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
                                    cap[i * m + j][ni * m + nj] = 1;
                                    cost[i * m + j][ni * m + nj] = (a[i][j] == a[ni][nj] ? 0 : 1);
                                    cost[ni * m + nj][i * m + j] = -cost[i * m + j][ni * m + nj];
                                }
                            }
                } else {
                    cap[i * m + j][t] = 1;
                }
            }
        FlowRes fr = minCostMaxFlow(nv, s, t, cap, cost);
        if (fr.flow != n * m / 2) throw new RuntimeException();
        out.println(fr.cost);
    }
    static class FlowRes {
        int flow;
        int cost;
    }

    static final int INF = (int) 1e9;

    private FlowRes minCostMaxFlow(int n, int s, int t, int[][] cap, int[][] cost) {
        int[] pri = new int[n];
        FlowRes res = new FlowRes();
        while (true) {
            int[] dist = new int[n];
            int[] prev = new int[n];
            Arrays.fill(prev, -1);
            Arrays.fill(dist, INF);
            dist[s] = 0;
            boolean[] mark = new boolean[n];
            int mxd = 0;
            while (true) {
                int bi = -1;
                for (int i = 0; i < n; ++i) if (dist[i] < INF / 2 && !mark[i]) {
                    if (bi < 0 || dist[i] < dist[bi])
                        bi = i;
                }
                if (bi < 0) break;
                mark[bi] = true;
                mxd = dist[bi];
                for (int i = 0; i < n; ++i) if (!mark[i] && cap[bi][i] > 0) {
                    int edge = pri[bi] - pri[i] + cost[bi][i];
                    if (dist[i] > dist[bi] + edge) {
                        dist[i] = dist[bi] + edge;
                        prev[i] = bi;
                    }
                }
            }
            if (!mark[t]) break;
            ++res.flow;
            res.cost += dist[t] - pri[s] + pri[t];
            int at = t;
            while (at != s) {
                int pat = prev[at];
                --cap[pat][at];
                ++cap[at][pat];
                at = pat;
            }
            for (int i = 0; i < n; ++i) {
                if (dist[i] > INF / 2) pri[i] += mxd; else pri[i] += dist[i];
            }
        }
        return res;
    }
}

class InputReader {
    public BufferedReader reader;
    public StringTokenizer tokenizer;

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

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

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

    }


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

public class C_new {

	BufferedReader br;
	PrintWriter out;
	StringTokenizer st;
	boolean eof;

	int n, m;

	class Edge {
		int to, flow, cap, cost;
		Edge rev;

		public Edge(int to, int cap, int cost) {
			this.to = to;
			this.cap = cap;
			this.cost = cost;
		}
	}

	static final int[] dx = { -1, 0, 1, 0 };
	static final int[] dy = { 0, -1, 0, 1 };
	static final int INF = Integer.MAX_VALUE / 3;

	List<Edge>[] g;

	void addEdge(int v1, int v2, int cost) {
		Edge e1 = new Edge(v2, 1, cost);
		Edge e2 = new Edge(v1, 0, -cost);
		e1.rev = e2;
		e2.rev = e1;
		g[v1].add(e1);
		g[v2].add(e2);
	}

	int S, T;

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

		int[][] f = new int[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				f[i][j] = nextInt();

		g = new ArrayList[n * m + 2];
		S = n * m;
		T = n * m + 1;
		for (int i = 0; i < n * m; i++)
			g[i] = new ArrayList<>(4);
		g[S] = new ArrayList<>(n * m / 2);
		g[T] = new ArrayList<>(n * m / 2);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				int v = i * m + j;
				int col = (i + j) & 1;
				if (col == 0) {
					addEdge(S, v, 0);
					for (int dir = 0; dir < 4; dir++) {
						int ni = i + dx[dir];
						int nj = j + dy[dir];
						if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
							int to = ni * m + nj;
							int cost = (f[i][j] == f[ni][nj] ? 0 : 1);
							addEdge(v, to, cost);
						}
					}
				} else {
					addEdge(v, T, 0);
				}
			}

		int ans = 0;

		final int[] d = new int[n * m + 2];
		Edge[] from = new Edge[n * m + 2];

		Integer[] Integers = new Integer[n * m + 2];
		for (int i = 0; i < n * m + 2; i++)
			Integers[i] = i;

		TreeSet<Integer> set = new TreeSet<>(new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				if (d[o1] != d[o2])
					return Integer.compare(d[o1], d[o2]);
				return o1.compareTo(o2);
			}

		});

		for (int iter = 0; iter < n * m / 2; iter++) {

			Arrays.fill(d, INF);
			d[S] = 0;

			for (int i = 0; i < n * m + 2; i++)
				set.add(Integers[i]);

			while (true) {
				int v = set.pollFirst();

//				System.err.println(v + " " + Arrays.toString(d));

				if (v == T) {
					break;
				}

				for (int i = 0; i < g[v].size(); i++) {
					Edge e = g[v].get(i);
					if (e.flow < e.cap) {
						if (d[e.to] > d[v] + e.cost) {
							set.remove(Integers[e.to]);
							from[e.to] = e;
							d[e.to] = d[v] + e.cost;
							set.add(Integers[e.to]);
						}
					}
				}
			}

			for (int v = T; v != S; ) {
				Edge last = from[v];
				last.flow++;
				last.rev.flow--;
				ans += last.cost;
				v = last.rev.to;
			}

		}

		out.println(ans);

	}

	C_new() throws IOException {
		br = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);
		solve();
		out.close();
	}

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

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

	String nextString() {
		try {
			return br.readLine();
		} catch (IOException e) {
			eof = true;
			return null;
		}
	}

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

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

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


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

public class C1 {

    int n, m, k;
    int[][] f;
    boolean[][] c;
    boolean[] mask;
    int best = Integer.MAX_VALUE;

    void calc(int i, int j, int cnt) {
        if (cnt < best) {
            if (i == n - 1 && j == m) {
                best = cnt;
            } else {
                if (j == m) {
                    j = 0;
                    ++i;
                }
                if (c[i][j]) {
                    calc(i, j + 1, cnt);
                } else {
                    c[i][j] = true;
                    int a = f[i][j];
                    if (j < m - 1 && !c[i][j + 1]) {
                        int b = f[i][j + 1];
                        c[i][j + 1] = true;
                        mask[a] = true;
                        calc(i, j + 2, a == b ? cnt : cnt + 1);
                        mask[a] = false;
                        /*
                        if (a != b) {
                            mask[b] = true;
                            calc(i, j + 2, cnt + 1);
                            mask[b] = false;
                        }
                        */
                        c[i][j + 1] = false;
                    }
                    if (i < n - 1 && !c[i + 1][j]) {
                        int b = f[i + 1][j];
                        c[i + 1][j] = true;
                        mask[a] = true;
                        calc(i, j + 1, a == b ? cnt : cnt + 1);
                        mask[a] = false;
                        /*
                        if (a != b) {
                            mask[b] = true;
                            calc(i, j + 1, cnt + 1);
                            mask[b] = false;
                        }
                        */
                        c[i + 1][j] = false;
                    }
                    c[i][j] = false;
                }
            }
        }
    }

    void solve() throws IOException {
        in("__std"); out("__std");

        n = readInt();
        m = readInt();
        k = n * m / 2;
        f = new int[n][m];
        c = new boolean[n][m];
        mask = new boolean[k];
        for (int i = 0; i < n; ++i) {
            for (int j  = 0; j < m; ++j) {
                f[i][j] = readInt() - 1;
            }
        }
        calc(0, 0, 0);
        println(best);

        exit();
    }

    void in(String name) throws IOException {
        if (name.equals("__std")) {
            in = new BufferedReader(new InputStreamReader(System.in));
        } else {
            in = new BufferedReader(new FileReader(name));
        }
    }

    void out(String name) throws IOException {
        if (name.equals("__std")) {
            out = new PrintWriter(System.out);
        } else {
            out = new PrintWriter(name);
        }
    }

    void exit() {
        out.close();
        System.exit(0);
    }

    char readChar() throws IOException {
        return (char) in.read();
    }

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

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

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

    String readLine() throws IOException {
        st = null;
        return in.readLine();
    }

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

    boolean eof() throws IOException {
        return !in.ready();
    }

    void print(String format, Object ... args) {
        out.print(new Formatter(Locale.US).format(format, args));
    }

    void println(String format, Object ... args) {
        out.println(new Formatter(Locale.US).format(format, args));
    }

    void print(Object value) {
        out.print(value);
    }

    void println(Object value) {
        out.println(value);
    }

    void println() {
        out.println();
    }

    StringTokenizer st;

    BufferedReader in;
    PrintWriter out;

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


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

public class C {

	BufferedReader br;
	PrintWriter out;
	StringTokenizer st;
	boolean eof;

	int n, m;

	class Edge {
		int to, flow, cap, cost;
		Edge rev;

		public Edge(int to, int cap, int cost) {
			this.to = to;
			this.cap = cap;
			this.cost = cost;
		}
	}

	static final int[] dx = { -1, 0, 1, 0 };
	static final int[] dy = { 0, -1, 0, 1 };
	static final int INF = Integer.MAX_VALUE / 3;

	List<Edge>[] g;

	void addEdge(int v1, int v2, int cost) {
		Edge e1 = new Edge(v2, 1, cost);
		Edge e2 = new Edge(v1, 0, -cost);
		e1.rev = e2;
		e2.rev = e1;
		g[v1].add(e1);
		g[v2].add(e2);
	}

	int S, T;

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

		int[][] f = new int[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				f[i][j] = nextInt();

		g = new ArrayList[n * m + 2];
		S = n * m;
		T = n * m + 1;
		for (int i = 0; i < n * m; i++)
			g[i] = new ArrayList<>(5);
		g[S] = new ArrayList<>(n * m / 2);
		g[T] = new ArrayList<>(n * m / 2);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				int v = i * m + j;
				int col = (i + j) & 1;
				if (col == 0) {
					addEdge(S, v, 0);
					for (int dir = 0; dir < 4; dir++) {
						int ni = i + dx[dir];
						int nj = j + dy[dir];
						if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
							int to = ni * m + nj;
							int cost = (f[i][j] == f[ni][nj] ? 0 : 1);
							addEdge(v, to, cost);
						}
					}
				} else {
					addEdge(v, T, 0);
				}
			}

		int ans = 0;
		int[] pi = new int[n * m + 2];

		final int[] d = new int[n * m + 2];
		Edge[] from = new Edge[n * m + 2];

		Integer[] Integers = new Integer[n * m + 2];
		for (int i = 0; i < n * m + 2; i++)
			Integers[i] = i;

		TreeSet<Integer> set = new TreeSet<>(new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				if (d[o1] != d[o2])
					return Integer.compare(d[o1], d[o2]);
				return o1.compareTo(o2);
			}

		});

		int[] permLabel = new int[n * m + 2];
		int permLabelSz = 0;

		for (int iter = 0; iter < n * m / 2; iter++) {

			Arrays.fill(d, INF);
			d[S] = 0;

			set.add(Integers[S]);

			permLabelSz = 0;

			while (true) {
				int v = set.pollFirst();

				if (v == T) {
					set.clear();
					break;
				}

				permLabel[permLabelSz++] = v;

				for (int i = 0; i < g[v].size(); i++) {
					Edge e = g[v].get(i);
					if (e.flow < e.cap) {
						int redCost = e.cost - pi[v] + pi[e.to];
						if (redCost < 0)
							throw new AssertionError();
						if (d[e.to] > d[v] + redCost) {
							set.remove(Integers[e.to]);
							from[e.to] = e;
							d[e.to] = d[v] + redCost;
							set.add(Integers[e.to]);
						}
					}
				}
			}

			for (int i = 0; i < permLabelSz; i++) {
				int v = permLabel[i];
				pi[v] -= d[v] - d[T];
			}

			for (int v = T; v != S; ) {
				Edge last = from[v];
				last.flow++;
				last.rev.flow--;
				ans += last.cost;
				v = last.rev.to;
			}

		}

		out.println(ans);

	}

	C() throws IOException {
		br = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);
		solve();
		out.close();
	}

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

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

	String nextString() {
		try {
			return br.readLine();
		} catch (IOException e) {
			eof = true;
			return null;
		}
	}

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

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

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


import java.util.*;
import java.io.*;
import java.math.*;
import java.awt.geom.*;

import static java.lang.Math.*;

public class Solution implements Runnable {

	class Edge {

		int to, capacity, cost;
		int flow = 0;
		Edge compl = null;

		public Edge(int to, int capacity, int cost) {
			this.to = to;
			this.capacity = capacity;
			this.cost = cost;
		}

	}

	int n, m;
	Edge[][] graph;
	int[] size;

	void addEdge(int from, int to, int cost) {
		Edge e1 = new Edge(to, 1, cost);
		Edge e2 = new Edge(from, 0, -cost);
		e1.compl = e2;
		e2.compl = e1;
		graph[from][size[from]++] = e1;
		graph[to][size[to]++] = e2;
	}

	boolean inBounds(int i, int j) {
		return i >= 0 && i < n && j >= 0 && j < m;
	}

	int getIndex(int i, int j) {
		return i * m + j;
	}

	void randomShuffle(int[] arr) {
		Random rnd = new Random();
		for (int i = arr.length - 1; i >= 0; i--) {
			int pos = rnd.nextInt(i + 1);
			int temp = arr[pos];
			arr[pos] = arr[i];
			arr[i] = temp;
		}
	}

	public void solve() throws Exception {
		n = sc.nextInt();
		m = sc.nextInt();
		int[][] field = new int[n][m];

		int[] arr = new int[n * m];
		for (int i = 0; i < n * m; i += 2) {
			arr[i] = arr[i + 1] = i / 2 + 1;
		}

		randomShuffle(arr);

		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				field[i][j] = sc.nextInt();
			}
		}
		graph = new Edge[n * m + 2][20];
		size = new int[n * m + 2];
		int source = n * m;
		int sink = n * m + 1;
		graph[source] = new Edge[n * m];
		graph[sink] = new Edge[n * m];
		int gsize = n * m + 2;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if ((i + j) % 2 == 0) {
					addEdge(source, getIndex(i, j), 0);
					if (inBounds(i - 1, j)) {
						if (field[i - 1][j] == field[i][j]) {
							addEdge(getIndex(i, j), getIndex(i - 1, j), 0);
						} else {
							addEdge(getIndex(i, j), getIndex(i - 1, j), 1);
						}
					}
					if (inBounds(i + 1, j)) {
						if (field[i + 1][j] == field[i][j]) {
							addEdge(getIndex(i, j), getIndex(i + 1, j), 0);
						} else {
							addEdge(getIndex(i, j), getIndex(i + 1, j), 1);
						}
					}
					if (inBounds(i, j - 1)) {
						if (field[i][j - 1] == field[i][j]) {
							addEdge(getIndex(i, j), getIndex(i, j - 1), 0);
						} else {
							addEdge(getIndex(i, j), getIndex(i, j - 1), 1);
						}
					}
					if (inBounds(i, j + 1)) {
						if (field[i][j + 1] == field[i][j]) {
							addEdge(getIndex(i, j), getIndex(i, j + 1), 0);
						} else {
							addEdge(getIndex(i, j), getIndex(i, j + 1), 1);
						}
					}
				} else {
					addEdge(getIndex(i, j), sink, 0);
				}
			}
		}

		int[] dist = new int[gsize];
		int[] from = new int[gsize];
		Edge[] byEdge = new Edge[gsize];
		Deque<Integer> q = new ArrayDeque<Integer>();

		int cost = 0;
		int flow = 0;

		while (true) {
			Arrays.fill(dist, Integer.MAX_VALUE / 4);
			q.clear();
			Arrays.fill(from, -1);
			Arrays.fill(byEdge, null);
			dist[source] = 0;
			q.offer(source);
			while (!q.isEmpty()) {
				int v = q.poll();
				for (int i = 0; i < size[v]; i++) {
					Edge e = graph[v][i];
					if (e.capacity - e.flow > 0 && dist[e.to] > dist[v] + e.cost) {
						dist[e.to] = dist[v] + e.cost;
						from[e.to] = v;
						byEdge[e.to] = e;
						if (e.cost == 0) {
							q.offerFirst(e.to);
						} else {
							q.offerLast(e.to);
						}
					}
				}
			}
			if (dist[sink] == Integer.MAX_VALUE / 4) {
				break;
			}
			cost += dist[sink];
			flow++;
			int curv = sink;
			while (from[curv] != -1) {
				Edge e = byEdge[curv];
				Edge compl = e.compl;
				e.flow++;
				compl.flow--;
				curv = from[curv];
			}
		}

		out.println(cost);
	}

	static Throwable uncaught;

	BufferedReader in;
	FastScanner sc;
	PrintWriter out;

	@Override
	public void run() {
		try {
			in = new BufferedReader(new InputStreamReader(System.in));
			out = new PrintWriter(System.out);
			sc = new FastScanner(in);
			solve();
		} catch (Throwable uncaught) {
			Solution.uncaught = uncaught;
		} finally {
			out.close();
		}
	}

	public static void main(String[] args) throws Throwable {
		Thread thread = new Thread(null, new Solution(), "", (1 << 26));
		thread.start();
		thread.join();
		if (Solution.uncaught != null) {
			throw Solution.uncaught;
		}
	}

}

class FastScanner {

	BufferedReader in;
	StringTokenizer st;

	public FastScanner(BufferedReader in) {
		this.in = in;
	}

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

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

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

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

}


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

public class C {

	BufferedReader br;
	PrintWriter out;
	StringTokenizer st;
	boolean eof;

	int n, m;

	class Edge {
		int to, flow, cap, cost;
		Edge rev;

		public Edge(int to, int cap, int cost) {
			this.to = to;
			this.cap = cap;
			this.cost = cost;
		}
	}

	static final int[] dx = { -1, 0, 1, 0 };
	static final int[] dy = { 0, -1, 0, 1 };
	static final int INF = Integer.MAX_VALUE / 3;

	List<Edge>[] g;

	void addEdge(int v1, int v2, int cost) {
		Edge e1 = new Edge(v2, 1, cost);
		Edge e2 = new Edge(v1, 0, -cost);
		e1.rev = e2;
		e2.rev = e1;
		g[v1].add(e1);
		g[v2].add(e2);
	}

	int S, T;

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

		int[][] f = new int[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				f[i][j] = nextInt();

		g = new ArrayList[n * m + 2];
		S = n * m;
		T = n * m + 1;
		for (int i = 0; i < n * m; i++)
			g[i] = new ArrayList<>(5);
		g[S] = new ArrayList<>(n * m / 2);
		g[T] = new ArrayList<>(n * m / 2);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				int v = i * m + j;
				int col = (i + j) & 1;
				if (col == 0) {
					addEdge(S, v, 0);
					for (int dir = 0; dir < 4; dir++) {
						int ni = i + dx[dir];
						int nj = j + dy[dir];
						if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
							int to = ni * m + nj;
							int cost = (f[i][j] == f[ni][nj] ? 0 : 1);
							addEdge(v, to, cost);
						}
					}
				} else {
					addEdge(v, T, 0);
				}
			}

		int ans = 0;
		int[] pi = new int[n * m + 2];

		final int[] d = new int[n * m + 2];
		Edge[] from = new Edge[n * m + 2];

		Integer[] Integers = new Integer[n * m + 2];
		for (int i = 0; i < n * m + 2; i++)
			Integers[i] = i;

		TreeSet<Integer> set = new TreeSet<>(new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				if (d[o1] != d[o2])
					return Integer.compare(d[o1], d[o2]);
				return o1.compareTo(o2);
			}

		});

		boolean[] permLabel = new boolean[n * m + 2];

		for (int iter = 0; iter < n * m / 2; iter++) {

			Arrays.fill(d, INF);
			d[S] = 0;

			set.add(Integers[S]);

			Arrays.fill(permLabel, false);

			while (true) {
				int v = set.pollFirst();

				if (v == T) {
					set.clear();
					break;
				}

				permLabel[v] = true;

				for (int i = 0; i < g[v].size(); i++) {
					Edge e = g[v].get(i);
					if (e.flow < e.cap) {
						int redCost = e.cost - pi[v] + pi[e.to];
						if (redCost < 0)
							throw new AssertionError();
						if (d[e.to] > d[v] + redCost) {
							set.remove(Integers[e.to]);
							from[e.to] = e;
							d[e.to] = d[v] + redCost;
							set.add(Integers[e.to]);
						}
					}
				}
			}

			for (int i = 0; i < n * m + 2; i++) {
				pi[i] -= (permLabel[i] ? d[i] : d[T]);
			}

			for (int v = T; v != S; ) {
				Edge last = from[v];
				last.flow++;
				last.rev.flow--;
				ans += last.cost;
				v = last.rev.to;
			}

		}

		out.println(ans);

	}

	C() throws IOException {
		br = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);
		solve();
		out.close();
	}

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

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

	String nextString() {
		try {
			return br.readLine();
		} catch (IOException e) {
			eof = true;
			return null;
		}
	}

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

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

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


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

public class C {

	BufferedReader br;
	PrintWriter out;
	StringTokenizer st;
	boolean eof;

	int n, m;

	class Edge {
		int to, flow, cap, cost;
		Edge rev;

		public Edge(int to, int cap, int cost) {
			this.to = to;
			this.cap = cap;
			this.cost = cost;
		}
	}

	static final int[] dx = { -1, 0, 1, 0 };
	static final int[] dy = { 0, -1, 0, 1 };
	static final int INF = Integer.MAX_VALUE / 3;

	List<Edge>[] g;

	void addEdge(int v1, int v2, int cost) {
		Edge e1 = new Edge(v2, 1, cost);
		Edge e2 = new Edge(v1, 0, -cost);
		e1.rev = e2;
		e2.rev = e1;
		g[v1].add(e1);
		g[v2].add(e2);
	}

	int S, T;

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

		int[][] f = new int[n][m];
		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++)
				f[i][j] = nextInt();

		g = new ArrayList[n * m + 2];
		S = n * m;
		T = n * m + 1;
		for (int i = 0; i < n * m; i++)
			g[i] = new ArrayList<>(4);
		g[S] = new ArrayList<>(n * m / 2);
		g[T] = new ArrayList<>(n * m / 2);

		for (int i = 0; i < n; i++)
			for (int j = 0; j < m; j++) {
				int v = i * m + j;
				int col = (i + j) & 1;
				if (col == 0) {
					addEdge(S, v, 0);
					for (int dir = 0; dir < 4; dir++) {
						int ni = i + dx[dir];
						int nj = j + dy[dir];
						if (ni >= 0 && ni < n && nj >= 0 && nj < m) {
							int to = ni * m + nj;
							int cost = (f[i][j] == f[ni][nj] ? 0 : 1);
							addEdge(v, to, cost);
						}
					}
				} else {
					addEdge(v, T, 0);
				}
			}

		int ans = 0;
		int[] pi = new int[n * m + 2];

		final int[] d = new int[n * m + 2];
		Edge[] from = new Edge[n * m + 2];

		Integer[] Integers = new Integer[n * m + 2];
		for (int i = 0; i < n * m + 2; i++)
			Integers[i] = i;

		TreeSet<Integer> set = new TreeSet<>(new Comparator<Integer>() {

			@Override
			public int compare(Integer o1, Integer o2) {
				if (d[o1] != d[o2])
					return Integer.compare(d[o1], d[o2]);
				return o1.compareTo(o2);
			}

		});

		for (int iter = 0; iter < n * m / 2; iter++) {

			Arrays.fill(d, INF);
			d[S] = 0;

			for (int i = 0; i < n * m + 2; i++)
				set.add(Integers[i]);

			while (!set.isEmpty()) {
				int v = set.pollFirst();

				for (int i = 0; i < g[v].size(); i++) {
					Edge e = g[v].get(i);
					if (e.flow < e.cap) {
						int redCost = e.cost - pi[v] + pi[e.to];
						if (d[e.to] > d[v] + redCost) {
							set.remove(Integers[e.to]);
							from[e.to] = e;
							d[e.to] = d[v] + redCost;
							set.add(Integers[e.to]);
						}
					}
				}
			}

			for (int i = 0; i < n * m + 2; i++) {
				pi[i] -= d[i];
			}

			for (int v = T; v != S; ) {
				Edge last = from[v];
				last.flow++;
				last.rev.flow--;
				ans += last.cost;
				v = last.rev.to;
			}

		}

		out.println(ans);

	}

	C() throws IOException {
		br = new BufferedReader(new InputStreamReader(System.in));
		out = new PrintWriter(System.out);
		solve();
		out.close();
	}

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

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

	String nextString() {
		try {
			return br.readLine();
		} catch (IOException e) {
			eof = true;
			return null;
		}
	}

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

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

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


import java.util.*;
public class Abby3_C {
public static void main(String[] args)
{
    Scanner input = new Scanner(System.in);
    int n = input.nextInt(), m = input.nextInt();
    int[][] data = new int[n][m];
    for(int i = 0; i<n; i++)
        for(int j = 0; j<m; j++)
            data[i][j] = input.nextInt();
    MinCostFlow mcf = new MinCostFlow(n*m+2);
    mcf.s = n*m;
    mcf.t = n*m+1;
    int[] di = new int[]{1, 0, -1, 0};
    int[] dj = new int[]{0, 1, 0, -1};
    for(int i = 0; i<n; i++)
    {
        for(int j = 0; j<m; j++)
        {
            if((i+j)%2 == 0)
            {
                mcf.add(mcf.s, i*m+j, 1, 0);
                for(int k = 0; k<4; k++)
                {
                    int ni = i+di[k], nj = j+dj[k];
                    if(ni<0||nj<0||ni>=n||nj>=m) continue;
                    int cost = data[i][j] == data[ni][nj] ? 0 : 1;
                    mcf.add(i*m+j, ni*m+nj, 1, cost);
                }
            }
            else
            {
                mcf.add(i*m+j, mcf.t, 1, 0);
            }
        }
    }
    System.out.println(mcf.minCostFlow()[1]);
}
public static class MinCostFlow{
    int N, s,t;
    ArrayList<Edge>[] adj;
    final int oo = 987654321;
    boolean negCosts;
    int[] pi,dist;
    Edge[] prev;

    public MinCostFlow(int n){
        N = n;
        adj = new ArrayList[N];
        for(int i = 0; i < N; i++)
            adj[i] = new ArrayList<Edge>();
        pi = new int[N];
        prev = new Edge[N];
        dist = new int[N];
    }

    void add(int u, int v, int cap, int cost){
        Edge e = new Edge(u,v,cap,cost);
        negCosts |= (cost < 0);
        Edge r = new Edge(v,u,0,cap,-cost);
        e.rev = r;
        r.rev = e;
        adj[u].add(e);
        adj[v].add(r);
    }

    public boolean dijkstra(){
        Arrays.fill(prev, null);
        Arrays.fill(dist,oo);
        PriorityQueue<Pair> q = new PriorityQueue<Pair>();
        dist[s] = 0;
        q.add(new Pair(s,0));
        while(q.size() > 0){
            Pair p = q.poll();
            int cur = p.first, d = p.second;
            if(dist[cur] < d)
                continue;
            for(Edge e: adj[cur])
                if(e.resid > 0 && dist[e.v] > dist[cur] + e.pcost()){
                    dist[e.v] = dist[cur] + e.pcost();
                    prev[e.v] = e;
                    q.add(new Pair(e.v, dist[e.v]));
                }
        }
        for(int i = 0; i < N; i++)
            if(pi[i] < oo)
                pi[i] += dist[i];
        return prev[t] != null;
    }

    public int[] minCostFlow(){
        int flow = 0, cost = 0;
        if(negCosts){
            Arrays.fill(dist,oo);
            dist[s] = 0;
            for(int k = 0; k < N-1; k++)
                for(int i = 0; i < N; i++)
                    for(Edge e:adj[i])
                        if(e.resid > 0 && dist[e.v] > dist[e.u] + e.cost)
                            dist[e.v] = dist[e.u] + e.cost;
            for(int i = 0; i < N; i++)
                if(pi[i] < oo)
                    pi[i] += dist[i];
        }
        while(dijkstra()){
            int f = Integer.MAX_VALUE/2;
            for(int cur = t; cur != s; cur = prev[cur].u)
                f = Math.min(f, prev[cur].resid);
            for(int cur = t; cur != s; cur = prev[cur].u){
                prev[cur].resid -= f;
                prev[cur].rev.resid += f;
                cost += prev[cur].cost * f;
            }
            flow += f;
        }
        return new int[]{flow,cost};
    }

    public class Edge{
        int u,v,resid,cap,cost;
        Edge rev;
        public Edge(int uu, int vv, int cp, int ct){
            u = uu;
            v = vv;
            resid = cap = cp;
            cost = ct;
        }
        public Edge(int uu, int vv, int rr, int cp, int ct){
            this(uu,vv,cp,ct);
            resid = rr;
        }
        int pcost(){
            return resid > 0?cost+pi[u]-pi[v]:0;
        }
    }
    public class Pair implements Comparable<Pair>{
        int first,second;
        public Pair(int ff, int ss){
            first = ff;
            second = ss;
        }
        public int compareTo(Pair p){
            return second-p.second;
        }
        public String toString(){
            return "("+first +", "+second+")";
        }
    }
}
}


import java.util.List;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.PriorityQueue;
import java.util.ArrayList;
import java.io.BufferedReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.StringTokenizer;
import java.util.Queue;
import java.util.Collection;
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);
		TaskC2 solver = new TaskC2();
		solver.solve(1, in, out);
		out.close();
	}
}

class TaskC2 {
	static class Edge {
		int to, f, cap, cost, rev;

		Edge(int v, int cap, int cost, int rev) {
			this.to = v;
			this.cap = cap;
			this.cost = cost;
			this.rev = rev;
		}
	}

	public static List<Edge>[] createGraph(int n) {
		List<Edge>[] graph = new List[n];
		for (int i = 0; i < n; i++)
			graph[i] = new ArrayList<Edge>();
		return graph;
	}

	public static void addEdge(List<Edge>[] graph, int s, int t, int cap, int cost) {
		graph[s].add(new Edge(t, cap, cost, graph[t].size()));
		graph[t].add(new Edge(s, 0, -cost, graph[s].size() - 1));
	}

	static void bellmanFord(List<Edge>[] graph, int s, int[] dist) {
		int n = graph.length;
		Arrays.fill(dist, 0, n, Integer.MAX_VALUE);
		dist[s] = 0;
		boolean[] inqueue = new boolean[n];
		int[] q = new int[n];
		int qt = 0;
		q[qt++] = s;
		for (int qh = 0; (qh - qt) % n != 0; qh++) {
			int u = q[qh % n];
			inqueue[u] = false;
			for (int i = 0; i < (int) graph[u].size(); i++) {
				Edge e = graph[u].get(i);
				if (e.cap <= e.f)
					continue;
				int v = e.to;
				int ndist = dist[u] + e.cost;
				if (dist[v] > ndist) {
					dist[v] = ndist;
					if (!inqueue[v]) {
						inqueue[v] = true;
						q[qt++ % n] = v;
					}
				}
			}
		}
	}

	static int[] minCostFlow(List<Edge>[] graph, int s, int t, int maxf) {
		int n = graph.length;
		int[] prio = new int[n];
		int[] curflow = new int[n];
		int[] prevedge = new int[n];
		int[] prevnode = new int[n];
		int[] pot = new int[n];

		// bellmanFord can be safely commented if edges costs are non-negative
		bellmanFord(graph, s, pot);
		int flow = 0;
		int flowCost = 0;
		while (flow < maxf) {
			Queue<Long> q = new PriorityQueue<Long>();
			q.add((long) s);
			Arrays.fill(prio, 0, n, Integer.MAX_VALUE);
			prio[s] = 0;
			curflow[s] = Integer.MAX_VALUE;
			while (!q.isEmpty()) {
				long cur = q.remove();
				int d = (int) (cur >>> 32);
				int u = (int) (cur & 0x7FFFFFFF);
				if (d != prio[u])
					continue;
				for (int i = 0; i < graph[u].size(); i++) {
					Edge e = graph[u].get(i);
					int v = e.to;
					if (e.cap <= e.f)
						continue;
					int nprio = prio[u] + e.cost + pot[u] - pot[v];
					if (prio[v] > nprio) {
						prio[v] = nprio;
						q.add(((long) nprio << 32) + v);
						prevnode[v] = u;
						prevedge[v] = i;
						curflow[v] = Math.min(curflow[u], e.cap - e.f);
					}
				}
			}
			if (prio[t] == Integer.MAX_VALUE)
				break;
			for (int i = 0; i < n; i++)
				pot[i] += prio[i];
			int df = Math.min(curflow[t], maxf - flow);
			flow += df;
			for (int v = t; v != s; v = prevnode[v]) {
				Edge e = graph[prevnode[v]].get(prevedge[v]);
				e.f += df;
				graph[v].get(e.rev).f -= df;
				flowCost += df * e.cost;
			}
		}
		return new int[]{flow, flowCost};
	}

	public void solve(int testNumber, InputReader in, PrintWriter out) {
		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();
			}
		}

		final List<Edge>[] g = createGraph(n * m + 2);
		int s = n * m;
		int t = s + 1;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				if ((i + j) % 2 == 1) {
					addEdge(g, s, i * m + j, 1, 0);
					int dx[] = {1, -1, 0, 0};
					int dy[] = {0, 0, 1, -1};
					for (int k = 0; k < 4; k++) {
						int ni = i + dx[k];
						int nj = j + dy[k];

						if (0 <= ni && ni < n && 0 <= nj && nj < m) {
							addEdge(g, i * m + j, ni * m + nj, 1, a[i][j] != a[ni][nj] ? 1 : 0);
						}
					}
				} else {
					addEdge(g, i * m + j, t, 1, 0);
				}
			}
		}
		int[] res = minCostFlow(g, s, t, Integer.MAX_VALUE);
		int flow = res[0];
		int flowCost = res[1];
		out.println(flowCost);
	}
}

class InputReader {
	public BufferedReader reader;
	public StringTokenizer tokenizer;

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

	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.*;
import java.util.*;

public class Main implements Runnable {

	public void _main() throws IOException {
		int height = nextInt();
		int width = nextInt();
		int[][] a = new int[height][width];
		for (int r = 0; r < height; r++) {
			for (int c = 0; c < width; c++) {
				a[r][c] = nextInt();
			}
		}
		Graph g = new Graph(height * width + 2);
		for (int r = 0; r < height; r++) {
			for (int c = 0; c < width; c++) {
				if ((r + c) % 2 == 0) {
					g.addEdge(g.s, r * width + c, 1, 0, -1);
				} else {
					g.addEdge(r * width + c, g.t, 1, 0, -1);
					continue;
				}
				for (int dr = -1; dr <= 1; dr++) {
					for (int dc = -1; dc <= 1; dc++) {
						if (dr == 0 ^ dc == 0) {
							int nr = r + dr;
							int nc = c + dc;
							if (nr < 0 || nr >= height || nc < 0 || nc >= width) continue;
							int cost = a[r][c] == a[nr][nc] ? 0 : 1;
							g.addEdge(r * width + c, nr * width + nc, 1, cost, -1);
						}
					}
				}
			}
		}
		out.println(g.minCostFlow());
	}

	class Graph {
		final int infinity = 1500000000;
		int n, s, t;

		final int N = 80 * 80 + 100;
		final int M = 8 * N;
		int[] firstEdge = new int[N];
		{
			Arrays.fill(firstEdge, -1);
		}
		int[] edgeFrom = new int[M];
		int[] edgeTo = new int[M];
		int[] edgeCap = new int[M];
		int[] edgeCost = new int[M];
		int[] edgeFlow = new int[M];
		int[] nextEdge = new int[M];
		int[] edgeId = new int[M];
		int numEdges;

		int[] heap = new int[N];
		int[] heapWhere = new int[N];
		int[] dist = new int[N];
		int heapSize;

		int[] prev = new int[N];
		int[] phi = new int[N];

		Graph(int n) {
			this.n = n;
			s = n - 1;
			t = n - 2;
		}
		void addEdge(int from, int to, int cap, int cost, int id) {
			int e;

			e = numEdges++;
			nextEdge[e] = firstEdge[from];
			firstEdge[from] = e;
			edgeFrom[e] = from;
			edgeTo[e] = to;
			edgeCap[e] = cap;
			edgeCost[e] = cost;
			edgeId[e] = id;

			e = numEdges++;
			nextEdge[e] = firstEdge[to];
			firstEdge[to] = e;
			edgeFrom[e] = to;
			edgeTo[e] = from;
			edgeCap[e] = 0;
			edgeCost[e] = -cost;
			edgeId[e] = id;
		}
		int minCostFlow() {
			/*
			for (int step = 0; step < n - 1; step++) {
				for (int i = 0; i < n; i++) {
					for (int e = firstEdge[i]; e >= 0; e = nextEdge[e]) {
						int j = edgeTo[e];
						if (phi[j] > phi[i] + edgeCost[e]) {
							phi[j] = phi[i] + edgeCost[e];
						}
					}
				}
			}
			*/
			int flow = 0;
			int flowCost = 0;
			Arrays.fill(heapWhere, 0, n, -1);
			while (true) {
				Arrays.fill(prev, 0, n, -1);
				Arrays.fill(dist, 0, n, infinity);
				dist[s] = 0;
				heapSize = 1;
				heapWhere[s] = 0;
				heap[0] = s;
				while (heapSize > 0) {
					int v = heap[0];
					if (heapSize > 1) {
						heapSwap(0, heapSize - 1);
						--heapSize;
						heapWhere[v] = -1;
						heapDown(0);
					} else {
						--heapSize;
						heapWhere[v] = -1;
					}
					for (int e = firstEdge[v]; e >= 0; e = nextEdge[e]) {
						if (edgeFlow[e] >= edgeCap[e]) continue;
						int u = edgeTo[e];
						if (dist[u] > dist[v] + edgeCost[e] + phi[v] - phi[u]) {
							dist[u] = dist[v] + edgeCost[e] + phi[v] - phi[u];
							prev[u] = e;
							if (heapWhere[u] < 0) {
								heapWhere[u] = heapSize++;
								heap[heapSize - 1] = u;
							}
							heapUp(heapWhere[u]);
							heapDown(heapWhere[u]);
						}
					}
				}
				if (prev[t] < 0) {
					break;
				}
				int dCost = dist[t] + phi[t] - phi[s];
				int v = t;
				while (v != s) {
					int e = prev[v];
					++edgeFlow[e];
					--edgeFlow[e ^ 1];
					v = edgeFrom[e];
				}
				++flow;
				flowCost += dCost;
				for (int i = 0; i < n; i++) {
					if (dist[i] < infinity) {
						phi[i] += dist[i];
					}
				}
			}
			return flowCost;
		}
		private void heapUp(int i) {
			while (i > 0) {
				int p = (i - 1) / 2;
				if (dist[i] < dist[p]) {
					heapSwap(i, p);
					i = p;
				} else {
					break;
				}
			}
		}
		private void heapDown(int i) {
			while (true) {
				int j = i;
				if (2*i + 1 < heapSize && dist[2*i + 1] < dist[j]) {
					j = 2*i + 1;
				}
				if (2*i + 2 < heapSize && dist[2*i + 2] < dist[j]) {
					j = 2*i + 2;
				}
				if (i == j) {
					break;
				} else {
					heapSwap(i, j);
					i = j;
				}
			}
		}
		private void heapSwap(int i, int j) {
			int ai = heap[i];
			int aj = heap[j];
			heapWhere[ai] = j;
			heapWhere[aj] = i;
			heap[i] = aj;
			heap[j] = ai;
		}

	}

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

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

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

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

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

	public static void main(String[] args) {
		Locale.setDefault(Locale.UK);
		new Thread(new Main()).start();
	}

	public void run() {
		try {
			in = new BufferedReader(new InputStreamReader(System.in));
			out = new PrintWriter(System.out);
			//in = new BufferedReader(new FileReader("a.in"));
			//out = new PrintWriter(new FileWriter("a.out"));

			_main();

			out.close();
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(202);
		}
	}

}


//package abbyy3;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.InputMismatchException;

public class C {
	InputStream is;
	PrintWriter out;
	String INPUT = "";

	void solve()
	{
		int n = ni(), m = ni();
		int[][] map = new int[n][m];
		for(int i = 0;i < n;i++){
			for(int j = 0;j < m;j++){
				map[i][j] = ni();
			}
		}

		int[][] dp = new int[n][1<<m];
		for(int i = 0;i < n;i++){
			Arrays.fill(dp[i], -99999);
		}
		dp[0] = makeRow(map[0]);
		for(int i = 1;i < n;i++){
			int[] row = makeRow(map[i]);
			for(int j = 0;j < 1<<m;j++){
				for(int k = 0;k < 1<<m;k++){ // k->j
					int inv = (~k&(1<<m)-1);
					if((inv&j) == inv){
						int nex = row[j^inv] + dp[i-1][k];
						for(int l = 0;l < m;l++){
							if(inv<<31-l<0 && map[i-1][l] == map[i][l]){
								nex++;
							}
						}
						dp[i][j] = Math.max(dp[i][j], nex);
					}
				}
			}
		}
		out.println(n*m/2-dp[n-1][(1<<m)-1]);
	}

	int[] makeRow(int[] r)
	{
		int m = r.length;
		int[] row = new int[1<<m];
		Arrays.fill(row, -99999);
		row[0] = 0;
		for(int i = 0;i < 1<<m;i++){
			for(int j = 3, k = 0;j < 1<<m;j*=2, k++){
				if((i&j) == j){
					if(row[i^j] != -99999){
						row[i] = row[i^j] + (r[k] == r[k+1] ? 1 : 0);
					}
					break;
				}
			}
		}
		return row;
	}

	void run() throws Exception
	{
		is = oj ? System.in : new ByteArrayInputStream(INPUT.getBytes());
		out = new PrintWriter(System.out);

		long s = System.currentTimeMillis();
		solve();
		out.flush();
		tr(System.currentTimeMillis()-s+"ms");
	}

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

	private byte[] inbuf = new byte[1024];
	private int lenbuf = 0, ptrbuf = 0;

	private int readByte()
	{
		if(lenbuf == -1)throw new InputMismatchException();
		if(ptrbuf >= lenbuf){
			ptrbuf = 0;
			try { lenbuf = is.read(inbuf); } catch (IOException e) { throw new InputMismatchException(); }
			if(lenbuf <= 0)return -1;
		}
		return inbuf[ptrbuf++];
	}

	private boolean isSpaceChar(int c) { return !(c >= 33 && c <= 126); }
	private int skip() { int b; while((b = readByte()) != -1 && isSpaceChar(b)); return b; }

	private double nd() { return Double.parseDouble(ns()); }
	private char nc() { return (char)skip(); }

	private String ns()
	{
		int b = skip();
		StringBuilder sb = new StringBuilder();
		while(!(isSpaceChar(b))){ // when nextLine, (isSpaceChar(b) && b != ' ')
			sb.appendCodePoint(b);
			b = readByte();
		}
		return sb.toString();
	}

	private char[] ns(int n)
	{
		char[] buf = new char[n];
		int b = skip(), p = 0;
		while(p < n && !(isSpaceChar(b))){
			buf[p++] = (char)b;
			b = readByte();
		}
		return n == p ? buf : Arrays.copyOf(buf, p);
	}

	private char[][] nm(int n, int m)
	{
		char[][] map = new char[n][];
		for(int i = 0;i < n;i++)map[i] = ns(m);
		return map;
	}

	private int[] na(int n)
	{
		int[] a = new int[n];
		for(int i = 0;i < n;i++)a[i] = ni();
		return a;
	}

	private int ni()
	{
		int num = 0, b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private long nl()
	{
		long num = 0;
		int b;
		boolean minus = false;
		while((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
		if(b == '-'){
			minus = true;
			b = readByte();
		}

		while(true){
			if(b >= '0' && b <= '9'){
				num = num * 10 + (b - '0');
			}else{
				return minus ? -num : num;
			}
			b = readByte();
		}
	}

	private boolean oj = System.getProperty("ONLINE_JUDGE") != null;
	private void tr(Object... o) { if(!oj)System.out.println(Arrays.deepToString(o)); }
}


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

public class C {

	static void solve() throws IOException {
		int n = nextInt(), m = nextInt();
		int[][] what = new int[n][m];
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				what[i][j] = nextInt();
			}
		}
		Graph g = new Graph(n * m + 2);
		int[] dx = { 0, 0, 1, -1 };
		int[] dy = { 1, -1, 0, 0 };
		int start = n * m;
		int finish = n * m + 1;
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				int from = i * m + j;
				if (((i ^ j) & 1) != 0) {
					g.addEdge(from, finish, 1, 0);
					continue;
				} else {
					g.addEdge(start, from, 1, 0);
				}
				for (int dir = 0; dir < 4; dir++) {
					int ni = i + dx[dir];
					int nj = j + dy[dir];
					if (ni < 0 || ni >= n || nj < 0 || nj >= m) {
						continue;
					}
					int to = ni * m + nj;
					int cost = 1;
					if (what[i][j] == what[ni][nj]) {
						cost = 0;
					}
					g.addEdge(from, to, 1, cost);
				}
			}
		}
		long cost = g.minCostFlow(start, finish)[1];
		out.println(cost);
	}

	public static class QItem implements Comparable<QItem> {
		long d;
		int v;

		public QItem(long d, int v) {
			this.d = d;
			this.v = v;
		}

		public int compareTo(QItem q) {
			return d < q.d ? -1 : d > q.d ? 1 : 0;
		}
	}

	public static class Graph {
		public final int n;
		public List<Edge>[] edges;

		@SuppressWarnings("unchecked")
		public Graph(int n) {
			this.n = n;
			edges = new List[n];
			for (int i = 0; i < n; i++) {
				edges[i] = new ArrayList<Edge>();
			}
		}

		public void addEdge(int u, int v, int cap, int cost) {
			Edge e1 = new Edge(v, cap, cost);
			Edge e2 = new Edge(u, 0, -cost);
			e1.rev = e2;
			e2.rev = e1;
			edges[u].add(e1);
			edges[v].add(e2);
		}

		public void addUndirectedEdge(int u, int v, int cap, int cost) {
			Edge e1 = new Edge(v, cap, cost);
			Edge e2 = new Edge(u, cap, -cost);
			e1.rev = e2;
			e2.rev = e1;
			edges[u].add(e1);
			edges[v].add(e2);
		}

		public void clearFlow() {
			for (List<Edge> edgesList : edges) {
				for (Edge e : edgesList) {
					e.flow = 0;
				}
			}
		}

		// input: s - source, t - sink
		// output: maximum flow and minimum cost
		public long[] minCostFlow(int s, int t) {
			final long INF = 1L << 50;
			long flow = 0;
			long flowCost = 0;
			long[] u = new long[n];
			while (true) {
				Queue<QItem> q = new PriorityQueue<QItem>();
				q.add(new QItem(0, s));
				Edge[] p = new Edge[n];
				long[] d = new long[n];
				Arrays.fill(d, INF);
				d[s] = 0;
				while (!q.isEmpty()) {
					QItem cur = q.poll();
					if (cur.d != d[cur.v]) {
						continue;
					}
					for (Edge e : edges[cur.v]) {
						long nd = d[cur.v] + e.cost + u[cur.v] - u[e.to];
						if (e.cap > e.flow && d[e.to] > nd) {
							d[e.to] = nd;
							p[e.to] = e;
							q.add(new QItem(nd, e.to));
						}
					}
				}
				if (p[t] == null) {
					break;
				}
				for (int i = 0; i < n; i++) {
					if (p[i] != null) {
						u[i] += d[i];
					}
				}
				int min = Integer.MAX_VALUE;
				for (Edge e = p[t]; e != null; e = p[e.rev.to]) {
					min = Math.min(min, e.cap - e.flow);
				}

				flow += min;
				for (Edge e = p[t]; e != null; e = p[e.rev.to]) {
					e.flow += min;
					e.rev.flow -= min;
					flowCost += (long) min * e.cost;
				}
			}
			return new long[] { flow, flowCost };
		}
	}

	public static class Edge {
		public int to, cap, flow, cost;
		public Edge rev;

		public Edge(int to, int cap, int cost) {
			this.to = to;
			this.cap = cap;
			this.cost = cost;
		}
	}

	static BufferedReader br;
	static StringTokenizer st;
	static PrintWriter out;

	public static void main(String[] args) throws IOException {
		InputStream input = System.in;
		PrintStream output = System.out;
		File file = new File("c.in");
		if (file.exists() && file.canRead()) {
			input = new FileInputStream(file);
		}
		br = new BufferedReader(new InputStreamReader(input));
		out = new PrintWriter(output);
		solve();
		out.close();
	}

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

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

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

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