#pragma GCC optimize("Ofast")
#pragma GCC target("avx,avx2,fma")
#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx")
#include <bits/stdc++.h>
#define rep(i,n) for(int i=1;i<=n;++i)
#define pb push_back
#define mp make_pair
using namespace std;
const int maxn = 3000 + 5; // 单侧顶点的最大数目
// 二分图最大基数匹配
struct BPM {
int n, m; // 左右顶点个数
vector<int> G[maxn]; // 邻接表
int left[maxn]; // left[i]为右边第i个点的匹配点编号,-1表示不存在
bool T[maxn]; // T[i]为右边第i个点是否已标记
int right[maxn]; // 求最小覆盖用
bool S[maxn]; // 求最小覆盖用
void init(int n, int m) {
this->n = n;
this->m = m;
for(int i = 0; i < n; i++) G[i].clear();
}
void AddEdge(int u, int v) {
G[u].push_back(v);
//cout<<u<<" "<<v<<endl;
}
bool match(int u){
S[u] = true;
for(int i = 0; i < G[u].size(); i++) {
int v = G[u][i];
if (!T[v]){
T[v] = true;
if (left[v] == -1 || match(left[v])){
left[v] = u;
right[u] = v;
return true;
}
}
}
return false;
}
// 求最大匹配
int solve() {
memset(left, -1, sizeof(left));
memset(right, -1, sizeof(right));
int ans = 0;
for(int u = 0; u < n; u++) { // 从左边结点u开始增广
memset(S, 0, sizeof(S));
memset(T, 0, sizeof(T));
if(match(u)) ans++;
}
return ans;
}
int v[maxn];
void dfs(int x)
{
if (v[x])return;
v[x]=1;
for(auto p:G[x])
if (left[p]!=-1)dfs(left[p]);
}
int work()
{
for(int i=0;i<n;i++)v[i]=0;
for(int i=0;i<n;i++)
if (right[i]==-1)dfs(i);
int tot=0;
for(int i=0;i<n;i++)
if (!v[i])tot++;
return tot;
}
}G;
char s[110][110];
int id[110][110];
int n,m;
const int dx[4]={0,0,-1,1};
const int dy[4]={-1,1,0,0};
int work(int sign)
{
int tot1=0,tot2=0;
rep(i,n)rep(j,m)
{
if (s[i][j]=='#')continue;
if ((i+j)%2==sign)id[i][j]=tot1++; else id[i][j]=tot2++;
}
G.init(tot1,tot2);
rep(i,n)rep(j,m)
{
if (s[i][j]=='#')continue;
if ((i+j)%2!=sign)continue;
for(int p=0;p<4;p++)
{
int ti=i+dx[p],tj=j+dy[p];
if (ti<=0 ||ti>n ||tj<=0||tj>m)continue;
if (s[ti][tj]=='#')continue;
G.AddEdge(id[i][j],id[ti][tj]);
}
}
G.solve();
return tot1-G.work();
}
int main()
{
cin>>n>>m;
rep(i,n)scanf("%s",s[i]+1);
//cout<<work(0)<<endl;
cout<<work(0)+work(1)<<endl;
return 0;
}