import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Scanner;

/**
 * Solution to Borders.
 *
 * @author vanb
 */
public class borders_vanb
{

    /** The input. */
    private Scanner sc = new Scanner( System.in );

    /** The output. */
    private PrintStream ps = System.out;

    /** Number of rows in the image. */
    private int n;

    /** The Number of columns in the image. */
    private int m;

    /** The image. */
    private char image[][];

    /** The index of the graph node for each region. */
    private int index[][];

    /**
     * Floodfill.
     *
     * @param i the i of an (i,j) coordinate
     * @param j the j of an (i,j) coordinate
     * @param pixel the pixel value
     * @param value the value for the index array
     * @return true, if not yet floodfilled (and floodfilled by this call)
     */
    private boolean floodfill( int i, int j, char pixel, int value )
    {
        boolean filled = false;
        if( i>=0 && i<n && j>=0 && j<m && image[i][j]==pixel && index[i][j]==-1 )
        {
            index[i][j] = value;
            floodfill( i+1, j, pixel, value );
            floodfill( i-1, j, pixel, value );
            floodfill( i, j+1, pixel, value );
            floodfill( i, j-1, pixel, value );
            filled = true;
        }

        return filled;
    }

    /**
     * A node of a max flow graph.
     *
     * @author vanb
     */
    public class Node
    {

        /** Has the node been visited? */
        public boolean visited = false;

        /** List of edges out from this node. */
        public LinkedList<Edge> edges = new LinkedList<Edge>();

        /** If this node has been visited, what edge did we get here from? */
        public Edge gothere = null;

        /** The name. */
        public String name;

        /**
         * Instantiates a new node.
         *
         * @param name the name
         */
        public Node( String name )
        {
            this.name = name;
        }
    }

    /**
     * An edge of a max flow graph.
     *
     * @author vanb
     */
    public class Edge
    {

        /** The destination. */
        Node destination;

        /** The capacity. */
        int capacity;

        /** The dual. */
        Edge dual;

        /**
         * Instantiates a new edge.
         *
         * @param d the Destination Node
         * @param c the Capacity
         */
        public Edge( Node d, int c )
        {
            capacity = c;
            destination = d;
            dual = null;
        }
    }

    /** Remember pairs of nodes that have already been linked, so we don't re-make those links. */
    private HashSet<String> linked = new HashSet<String>();

    /**
     * Create a link between two nodes of a max flow graph.
     *
     * @param n1 From node
     * @param n2 To node
     * @param cost Cost to go from n1 to n2
     */
    public void link( Node n1, Node n2, int cost )
    {
        // Describe a link in a String
        String key = n1.name + "->" + n2.name;

        // Make sure we haven't already made this link
        if( !linked.contains(key) )
        {
            Edge e12 = new Edge( n2, cost );
            Edge e21 = new Edge( n1, 0 );
            e12.dual = e21;
            e21.dual = e12;
            n1.edges.add( e12 );
            n2.edges.add( e21 );
            linked.add( key );
        }
    }

    /**  Queue for the Ford/Fulkerson algorithm. */
    public LinkedList<Node> queue = new LinkedList<Node>();

    /**
     * Perform the Ford/Fulkerson algorithm on a graph.
     *
     * @param src Source node
     * @param snk Sink node
     * @param nodes The graph, represented as a list of nodes
     * @return The max flow from the source to the sink
     */
    public int fordfulkerson( Node src, Node snk, List<Node> nodes )
    {
        int total = 0;

        // Keep going until you can't get from the source to the sink
        for(;;)
        {
            // Reset the graph
            for( Node node : nodes )
            {
                node.visited = false;
                node.gothere = null;
            }

            // Reset the queue
            // Start at the source
            queue.clear();
            queue.add( src );
            src.visited = true;

            // Have we found the sink?
            boolean found = false;

            // Use a breadth-first search to find a path from the source to the sink
            while( queue.size()>0 )
            {
                Node node = queue.poll();

                // have we found the sink? If so, break out of the BFS.
                if( node==snk )
                {
                    found = true;
                    break;
                }

                // Look for edges to traverse
                for( Edge edge : node.edges )
                {
                    Node dest = edge.destination;

                    // If this destination hasn't been visited,
                    // and the edge has capacity,
                    // put it on the queue.
                    if( edge.capacity>0 && !dest.visited )
                    {
                        // Node has been visited
                        dest.visited = true;

                        // Remember the edge that got us here
                        dest.gothere = edge;

                        // Add to the queue
                        queue.add( dest );
                    }
                }
            }

            // If we were unable to get to the sink, then we're done
            if( !found ) break;

            // Otherwise, look along the path to find the minimum capacity
            int min = Integer.MAX_VALUE;
            for( Node node = snk; node.gothere != null; )
            {
                Edge edge = node.gothere;
                if( edge.capacity < min ) min = edge.capacity;
                node = edge.dual.destination;
            }

            // Add that minimum capacity to the total
            total += min;

            // Go back along the path, and for each edge, move the min
            // capacity from the edge to its dual.
            for( Node node = snk; node.gothere != null; )
            {
                Edge edge = node.gothere;
                edge.capacity -= min;
                edge.dual.capacity += min;
                node = edge.dual.destination;
            }
        }

        // Return the total
        return total;
    }

    /**
     * Determine if this region is linkable to a region under consideration.
     *
     * @param rtest the index of the region we're testing
     * @param rconsider the index of the region we're considering
     * @return true, if we can link the region we're testing to the region we're considering
     */
    private boolean linkable( int rtest, int rconsider )
    {
        // Check to make sure index is >=0 (that is, (i,i) is not on a border region)
        // and also that (i,j) is in a different region that the one we're looking at.
        return rtest>=0 && rtest!=rconsider;
    }

    /**
     * Do it!
     */
    private void doit()
    {
        n = sc.nextInt();
        m = sc.nextInt();

        image = new char[n][m];
        index = new int[n][m];

        // Read in the image, initialize the index array
        for( int i=0; i<n; i++ )
        {
            Arrays.fill( index[i], -1 );
            image[i] = sc.next().toCharArray();
        }

        // Handle top & bottom edges
        int count = 0;
        for( int i=0; i<n; i++ )
        {
            if( floodfill( i, 0, image[i][0], -2 ) ) ++count;
            if( floodfill( i, m-1, image[i][m-1], -2 ) ) ++count;
        }

        // Handle left & right edges
        for( int j=0; j<m; j++ )
        {
            if( floodfill( 0, j, image[0][j], -2 ) ) ++count;
            if( floodfill( n-1, j, image[n-1][j], -2 ) ) ++count;
        }

        // Assign node numbers to regions
        int nodenum = 0;
        for( int i=1; i<n-1; i++ ) for( int j=1; j<m-1; j++ )
        {
            if( floodfill( i, j, image[i][j], nodenum ) ) ++nodenum;
        }

        // Create nodes
        List<Node> nodes = new ArrayList<Node>(nodenum+2);
        Node source = new Node( "source" );
        nodes.add( source );
        Node sink = new Node( "sink" );
        nodes.add( sink );
        Node regions[] = new Node[nodenum];
        for( int i=0; i<nodenum; i++ )
        {
            regions[i] = new Node( ""+i );
            nodes.add( regions[i] );
        }

        // Now, create links
        for( int i=1; i<n-1; i++ ) for( int j=1; j<m-1; j++ )
        {
            if( index[i][j]>=0 )
            {
                if( image[i][j]=='0' )
                {
                    link( source, regions[index[i][j]], 1 );
                    if( linkable( index[i-1][j], index[i][j] ) ) link( regions[index[i][j]], regions[index[i-1][j]], 1 );
                    if( linkable( index[i][j-1], index[i][j] ) ) link( regions[index[i][j]], regions[index[i][j-1]], 1 );
                }
                else
                {
                    link( regions[index[i][j]], sink, 1 );
                    if( linkable( index[i-1][j], index[i][j] ) ) link( regions[index[i-1][j]], regions[index[i][j]], 1 );
                    if( linkable( index[i][j-1], index[i][j] ) ) link( regions[index[i][j-1]], regions[index[i][j]], 1 );
                }
            }
        }

        ps.println( count + fordfulkerson( source, sink, nodes ) );
    }

    /**
     * The main method.
     *
     * @param args the arguments
     */
    public static void main( String[] args )
    {
        new borders_vanb().doit();
    }

}
