// #pragma GCC optimize("Ofast")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
// #pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
// #define int long long
using namespace std;

#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
#define rep1(i, n) for (int i = 1; i <= (int)(n); ++i)
#define range(x) begin(x), end(x)
#define sz(x) (int)(x).size()
#define pb push_back
#define F first
#define S second

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
typedef vector<int> vi;
/*
计算u^v % mod
*/
ll power(ll u, ll v, ll mod) {
  ll ret = 1, t = u % mod;
  while (v) {
    if (v & 1)
      ret = t * ret % mod;
    t = t * t % mod, v >>= 1;
  }
  return ret % mod;
}

/*
计算gcd(a,b)并找寻ax+by=gcd(a,b)的一组解
ret=exgcd(a,b,x,y);
*/
ll exgcd(ll a, ll b, ll &x, ll &y) {
  if (b) {
    ll d = exgcd(b, a % b, y, x);
    return y -= a / b * x, d;
  }
  return x = 1, y = 0, a;
}

/*
计算x=a(mod m), x=b(mod n)的一组解，若无解返回-1
ret=crt(a,m,b,n);
*/
ll crt(ll a, ll m, ll b, ll n) {
  if (n > m)
    swap(a, b), swap(n, m);
  ll x, y, g = exgcd(m, n, x, y);
  if ((b - a) % g)
    return -1;
  x = (b - a) % n * x % n / g * m + a;
  return x < 0 ? x + m * n / g : x;
}

/*
生成<=n的质数
gen_prime<5000>(ret);
*/

void gen_prime(int n, vi &ret) {
  vector<bool> p;
  p.resize(n + 1, 1);
  p[0] = p[1] = 0;
  for (int i = 4; i <= n; i += 2)
    p[i] = 0;
  for (int i = 3; i * i <= n; i += 2) {
    if (p[i]) {
      for (int j = i * i; j <= n; j += 2 * i) {
        p[j] = 0;
      }
    }
  }
  for (int i = 2; i <= n; ++i) {
    if (p[i])
      ret.pb(i);
  }
}

/*
生成<=n的phi
gen_phi<5000>(ret);
*/
void gen_phi(int n, vi &phi) {
  phi.resize(n + 1);
  for (int i = 0; i <= n; ++i)
    phi[i] = i & 1 ? i : i / 2;
  for (int i = 3; i <= n; i += 2) {
    if (phi[i] == i) {
      for (int j = i; j <= n; j += i)
        phi[j] -= phi[j] / i;
    }
  }
}

/*
支持更高的快速幂操作
expower.mod_pow(a,b,mod);
*/

struct expower {
  ull mod_mul(ull a, ull b, ull M) {
    ll ret = a * b - M * ull(ld(a) * ld(b) / ld(M));
    return ret + M * (ret < 0) - M * (ret >= (ll)M);
  }
  ull mod_pow(ull b, ull e, ull mod) {
    ull ans = 1;
    for (; e; b = mod_mul(b, b, mod), e /= 2)
      if (e & 1)
        ans = mod_mul(ans, b, mod);
    return ans % mod;
  }
} expower;

/*
Miller-Rubin素数判别
is_prime(n);
*/
bool is_prime(ull n) {
  if (n < 2 || n % 6 % 4 != 1)
    return n - 2 < 2;
  ull A[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022},
      s = __builtin_ctzll(n - 1), d = n >> s;
  for (auto a : A) {
    ull p = expower.mod_pow(a, d, n), i = s;
    while (p != 1 && p != n - 1 && a % n && i--)
      p = expower.mod_mul(p, p, n);
    if (p != n - 1 && i != s)
      return 0;
  }
  return 1;
}

/*
素因数分解
ret=factorization(n);
*/
ull pollard(ull n) {
  auto f = [n](ull x) { return (expower.mod_mul(x, x, n) + 1) % n; };
  if (!(n & 1))
    return 2;
  for (ull i = 2;; i++) {
    ull x = i, y = f(x), p;
    while ((p = __gcd(n + y - x, n)) == 1)
      x = f(x), y = f(f(y));
    if (p != n)
      return p;
  }
}

vector<ull> factorization(ull n) {
  if (n == 1)
    return {};
  if (is_prime(n))
    return {n};
  ull x = pollard(n);
  auto l = factorization(x), r = factorization(n / x);
  l.insert(l.end(), range(r));
  return l;
}

struct DSU {
  // insert info.
  vector<int> p;
  DSU(int n) {
    p.resize(n + 1);
    for (int i = 1; i <= n; ++i)
      p[i] = i;
  }
  int find(int u) {
    if (p[u] == u)
      return u;
    return p[u] = find(find(p[u]));
  }
  bool unite(int u, int v) {
    int pu = find(u), pv = find(v);
    if (pu != pv) {
      p[pu] = pv;
    }
    return pu != pv;
  }
};

/* (u,v,w) */
template <class T>
vector<tuple<int, int, T>> MST(int n, vector<tuple<int, int, T>> g) {
  vector<tuple<int, int, T>> ret;
  ret.clear();
  DSU dsu(n);
  auto cmp = [&](tuple<int, int, T> x, tuple<int, int, T> y) {
    int ux, uy, vx, vy;
    T wx, wy;
    tie(ux, vx, wx) = x, tie(uy, vy, wy) = y;
    return wx < wy;
  };
  sort(range(g), cmp);
  for (auto c : g) {
    int u, v;
    T w;
    tie(u, v, w) = c;
    if (dsu.unite(u, v))
      ret.push_back(c);
  }
  return ret;
}

template <int n, class T> struct SegmentTree {
  typedef struct {
    T lz, mx, mn, sum;
  } Node;
  vector<Node> tree;
  SegmentTree() { tree.resize((n + 7) << 2); }
  void pushup(int num) {
    tree[num].mx = max(tree[num << 1].mx, tree[num << 1 | 1].mx);
    tree[num].mn = min(tree[num << 1].mn, tree[num << 1 | 1].mn);
    tree[num].sum = tree[num << 1].sum + tree[num << 1 | 1].sum;
  }
  void pushdown(int num, int l, int r, int md) {
    if (tree[num].lz) {
      tree[num << 1].mx += tree[num].lz;
      tree[num << 1 | 1].mx += tree[num].lz;
      tree[num << 1].mn += tree[num].lz;
      tree[num << 1 | 1].mn += tree[num].lz;
      tree[num << 1].sum += tree[num].lz * (md - l + 1);
      tree[num << 1 | 1].sum += tree[num].lz * (r - md);
      tree[num << 1].lz += tree[num].lz;
      tree[num << 1 | 1].lz += tree[num].lz;
      tree[num].lz = 0;
    }
  }
  void update1(int num, int l, int r, int L, int R, T val) {
    if (L <= l && r <= R) {
      tree[num].mx += val;
      tree[num].mn += val;
      tree[num].sum += val * (r - l + 1);
      tree[num].lz += val;
      return;
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    if (md >= L)
      update1(num << 1, l, md, L, R, val);
    if (md < R)
      update1(num << 1 | 1, md + 1, r, L, R, val);
    pushup(num);
  }
  tuple<T, T, T> query1(int num, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
      return {tree[num].mn, tree[num].mx, tree[num].sum};
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    if (md >= R) {
      return query1(num << 1, l, md, L, R);
    } else {
      if (md < L) {
        return query1(num << 1 | 1, md + 1, r, L, R);
      } else {
        tuple<T, T, T> ret1 = query1(num << 1, l, md, L, R),
                       ret2 = query1(num << 1 | 1, md + 1, r, L, R);
        T a, b, c, d, e, f;
        tie(a, b, c) = ret1, tie(d, e, f) = ret2;
        return {min(a, d), max(b, e), c + f};
      }
    }
  }
  void update(int L, int R, int w) { update1(1, 1, n, L, R, w); }
  tuple<T, T, T> query(int L, int R) { return query1(1, 1, n, L, R); }
  void debug1(int num, int l, int r) {
    if (l == r) {
      cout << tree[num].mn << " ";
      return;
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    debug1(num << 1, l, md);
    debug1(num << 1 | 1, md + 1, r);
  }
  void debug() {
    debug1(1, 1, n);
    cout << endl;
  }
};

struct XORBasis {
  int n;
  vector<ull> p;
  vector<ull> ans;
  XORBasis(int k) : n(k) { p.resize(k + 1); }
  void rebuilt() {
    ans.clear();
    for (int i = 0; i <= n; ++i) {
      if (p[i])
        ans.push_back(p[i]);
    }
  }
  void insert(ull u) {
    for (int i = n; i >= 0; --i) {
      if (u & (1ull << i)) {
        if (p[i])
          u ^= p[i];
        else {
          p[i] = u;
          for (int j = i - 1; j >= 0; --j) {
            if (p[i] & (1ull << j))
              p[i] ^= p[j];
          }
          for (int j = i + 1; j <= n; ++j) {
            if (p[j] & (1ull << i))
              p[j] ^= p[i];
          }
          break;
        }
      }
    }
    rebuilt();
  }
  bool check(ull u) {
    for (int i = n; i > -1; --i) {
      if (u & (1ull << i))
        u ^= p[i];
    }
    return (u == 0);
  }
  ull kth(ull t) { // the k-th smallest, excluding 0.
    if (t >= (1ull << sz(ans)))
      return -1;
    ull ret = 0;
    for (int i = 0; i < sz(ans); ++i) {
      if (t & (1ull << i))
        ret ^= ans[i];
    }
    return ret;
  }
  ull total() { return (1ull << sz(ans)); }
};

vi pi(const string &s) {
  vi p(sz(s));
  for (int i = 1; i <= sz(s); ++i) {
    int g = p[i - 1];
    while (g && s[i] != s[g])
      g = p[g - 1];
    p[i] = g + (s[i] == s[g]);
  }
  return p;
}

vi match(const string &s, const string &pat) {
  vi p = pi(pat + '\\0' + s), res;
  for (int i = sz(p) - sz(s); i <= sz(p); ++i)
    if (p[i] == sz(pat))
      res.push_back(i - 2 * sz(pat));
  return res;
}

struct AhoCorasick {
  enum { alpha = 26, first = 'a' };
  struct Node {
    // (nmatches is optional)
    int back, next[alpha], start = -1, end = -1;
    ll nmatches = 0;
    Node(int v) { memset(next, v, sizeof(next)); }
  };
  vector<Node> N;
  vector<int> backp;
  void insert(string &s, int j, ll val) {
    assert(!s.empty());
    int n = 0;
    for (auto c : s) {
      int &m = N[n].next[c - first];
      if (m == -1) {
        n = m = sz(N);
        N.emplace_back(-1);
      } else
        n = m;
    }
    if (N[n].end == -1)
      N[n].start = j;
    backp.push_back(N[n].end);
    N[n].end = j;
    N[n].nmatches += val;
  }
  AhoCorasick(vector<string> &pat, vi val) {
    N.emplace_back(-1);
    for (int i = 0; i < sz(pat); ++i)
      insert(pat[i], i, val[i]);
    N[0].back = sz(N);
    N.emplace_back(0);

    queue<int> q;
    for (q.push(0); !q.empty(); q.pop()) {
      int n = q.front(), prev = N[n].back;
      for (int i = 0; i < alpha; ++i) {
        int &ed = N[n].next[i], y = N[prev].next[i];
        if (ed == -1)
          ed = y;
        else {
          N[ed].back = y;
          (N[ed].end == -1 ? N[ed].end : backp[N[ed].start]) = N[y].end;
          N[ed].nmatches += N[y].nmatches;
          q.push(ed);
        }
      }
    }
  }
  vi find(string &word, ll &ans) {
    int n = 0;
    vi res;
    for (auto c : word) {
      n = N[n].next[c - first];
      res.push_back(N[n].end);
      ans += N[n].nmatches;
    }
    return res;
  }
  vector<vi> findAll(vector<string> &pat, string word) {
    ll ans = 0;
    vi r = find(word, ans);
    vector<vi> res(sz(word));
    for (int i = 0; i < sz(word); ++i) {
      int ind = r[i];
      while (ind != -1) {
        res[i - sz(pat[ind]) + 1].push_back(ind);
        ind = backp[ind];
      }
    }
    return res;
  }
};

struct HLD {
  int n, cnt = 0;
  vector<vi> G;
  vi par, dep, siz, son, top, dfn, rk;
  // 父亲节点，深度，子树大小，重子节点，重链头部，dfs序，dfs序对应的节点编号
  HLD(vector<vi> g, int root = 1)
      : n(sz(g)), G(g), par(n + 7), dep(n + 7), siz(n + 7, 1), son(n + 7, -1),
        top(n + 7), dfn(n + 7), rk(n + 7) {
    dep[0] = -1, dfs1(root, 0), dfs2(root, 0, root);
  }
  void dfs1(int u, int p) {
    for (auto c : G[u]) {
      if (c == p)
        continue;
      dep[c] = dep[u] + 1, par[c] = u;
      dfs1(c, u);
      siz[u] += siz[c];
      if (son[u] == -1 || siz[son[u]] < siz[c])
        son[u] = c;
    }
  }
  void dfs2(int u, int p, int tp) {
    top[u] = tp;
    dfn[u] = ++cnt;
    rk[cnt] = u;
    if (son[u] == -1)
      return;
    dfs2(son[u], u, tp);
    for (auto c : G[u]) {
      if (c == p || c == son[u])
        continue;
      dfs2(c, u, c);
    }
  }
  int lca(int u, int v) {
    while (top[u] != top[v]) {
      if (dep[top[u]] > dep[top[v]]) {
        u = par[top[u]];
      } else {
        v = par[top[v]];
      }
    }
    return dep[u] > dep[v] ? v : u;
  }
  vector<pii> find_seg(int u, int v) {
    vector<pii> ft, bk, res;
    ft.clear(), bk.clear(), res.clear();
    //        cout<<top[u]<<" "<<top[v]<<endl;
    while (top[u] != top[v]) {
      if (dep[top[u]] > dep[top[v]]) {
        ft.pb({u, top[u]});
        u = par[top[u]];
      } else {
        bk.pb({top[v], v});
        v = par[top[v]];
      }
    }
    ft.pb({u, v});
    for (int i = 0; i < sz(ft); ++i)
      res.pb(ft[i]);
    for (int i = sz(bk); i > 0; --i)
      res.pb(bk[i - 1]);
    return res;
  }
};

struct BIT {
  vi C;
  int n;
  BIT(int n) : n(n) { C.resize(n + 7, 0); }
  void update(int u, int w) {
    for (; u <= n; u += u & (-u))
      C[u] += w;
  }
  int query(int u) {
    int res = 0;
    for (; u > 0; u -= u & (-u))
      res += C[u];
    return res;
  }
};

vi topoSort(const vector<vi> &gr) {
  vi indeg(sz(gr)), ret;
  for (auto &li : gr)
    for (int x : li)
      indeg[x]++;
  queue<int> q; // use priority queue for lexic. smallest ans.
  rep(i, sz(gr)) if (indeg[i] == 0) q.push(-i);
  while (!q.empty()) {
    int i = -q.front(); // top() for priority queue
    ret.push_back(i);
    q.pop();
    for (int x : gr[i])
      if (--indeg[x] == 0)
        q.push(-x);
  }
  return ret;
}

vi path;
namespace Eulerian {
const int N = 3, M = 3;
int t, n, m, tot, cc, hd[N], nxt[M], to[M], bh[M], vis[M], in[N], out[N],
    ans[M];
void add(int x, int y, int id) {
  nxt[++tot] = hd[x];
  hd[x] = tot;
  to[tot] = y;
  bh[tot] = id;
  in[y]++;
  out[x]++;
}
void dfs(int x) {
  for (int i = hd[x]; i; i = hd[x]) {
    while (i && vis[abs(bh[i])])
      i = nxt[i];
    hd[x] = i;
    if (!i)
      break;
    vis[abs(bh[i])] = 1;
    dfs(to[i]), ans[++cc] = bh[i];
  }
}

void init() {
  memset(hd, 0, sizeof(hd));
  memset(nxt, 0, sizeof(nxt));
  memset(to, 0, sizeof(to));
  memset(bh, 0, sizeof(bh));
  memset(vis, 0, sizeof(vis));
  memset(in, 0, sizeof(in));
  memset(out, 0, sizeof(out));
  memset(ans, 0, sizeof(ans));
  t = 1;
  n = 0;
  m = 0;
  tot = 0;
  cc = 0;
}
bool solve() {
  for (int i = 1; i <= n; i++)
    if ((t == 1 && in[i] & 1)) {
      return false;
    }
  dfs(to[1]);
  if (cc < m)
    return false;
  else {
    path.clear();
    for (int i = cc; i >= 1; i--)
      path.push_back(ans[i]);
    return true;
  }
}
} // namespace Eulerian

namespace FWT {
// mod=-1 (no mod)
const int mod = -1;

inline int modadd(int a, int b) {
  a += b;
  if (mod == -1)
    return a;
  a %= mod, a += mod, a %= mod;
  return a;
}

vi OR(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1)
        b[j] = modadd(b[j], b[j ^ (1 << i)] * mode);
    }
  }
  return b;
}
vi AND(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1)
        b[j ^ (1 << i)] = modadd(b[j ^ (1 << i)], b[j] * mode);
    }
  }
  return b;
}
vi XOR(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1) {
        b[j ^ (1 << i)] = modadd(b[j ^ (1 << i)], b[j]);
        b[j] = modadd(b[j ^ (1 << i)], -2 * b[j]);
        if (mode < 0) {
          if (mod > 0)
            b[j] = b[j] * power(2, mod - 2, mod) % mod,
            b[j ^ (1 << i)] = b[j ^ (1 << i)] * power(2, mod - 2, mod) % mod;
          else
            b[j] /= 2, b[j ^ (1 << i)] /= 2;
        }
      }
    }
  }
  return b;
}
} // namespace FWT

/* atcoder mincost-maxflow */

template <class Cap, class Cost> struct mcf_graph {
public:
  mcf_graph() {}
  mcf_graph(int n) : _n(n), g(n) {}

  int add_edge(int from, int to, Cap cap, Cost cost) {
    assert(0 <= from && from < _n);
    assert(0 <= to && to < _n);
    int m = (int)(pos.size());
    pos.push_back({from, (int)(g[from].size())});
    g[from].push_back(_edge{to, (int)(g[to].size()), cap, cost});
    g[to].push_back(_edge{from, (int)(g[from].size()) - 1, 0, -cost});
    return m;
  }

  struct edge {
    int from, to;
    Cap cap, flow;
    Cost cost;
  };

  edge get_edge(int i) {
    int m = (int)(pos.size());
    assert(0 <= i && i < m);
    auto _e = g[pos[i].first][pos[i].second];
    auto _re = g[_e.to][_e.rev];
    return edge{
        pos[i].first, _e.to, _e.cap + _re.cap, _re.cap, _e.cost,
    };
  }
  std::vector<edge> edges() {
    int m = (int)(pos.size());
    std::vector<edge> result(m);
    for (int i = 0; i < m; i++) {
      result[i] = get_edge(i);
    }
    return result;
  }

  std::pair<Cap, Cost> flow(int s, int t) {
    return flow(s, t, std::numeric_limits<Cap>::max());
  }
  std::pair<Cap, Cost> flow(int s, int t, Cap flow_limit) {
    return slope(s, t, flow_limit).back();
  }
  std::vector<std::pair<Cap, Cost>> slope(int s, int t) {
    return slope(s, t, std::numeric_limits<Cap>::max());
  }
  std::vector<std::pair<Cap, Cost>> slope(int s, int t, Cap flow_limit) {
    assert(0 <= s && s < _n);
    assert(0 <= t && t < _n);
    assert(s != t);
    // variants (C = maxcost):
    // -(n-1)C <= dual[s] <= dual[i] <= dual[t] = 0
    // reduced cost (= e.cost + dual[e.from] - dual[e.to]) >= 0 for all edge
    std::vector<Cost> dual(_n, 0), dist(_n);
    std::vector<int> pv(_n), pe(_n);
    std::vector<bool> vis(_n);
    auto dual_ref = [&]() {
      std::fill(dist.begin(), dist.end(), std::numeric_limits<Cost>::max());
      std::fill(pv.begin(), pv.end(), -1);
      std::fill(pe.begin(), pe.end(), -1);
      std::fill(vis.begin(), vis.end(), false);
      struct Q {
        Cost key;
        int to;
        bool operator<(Q r) const { return key > r.key; }
      };
      std::priority_queue<Q> que;
      dist[s] = 0;
      que.push(Q{0, s});
      while (!que.empty()) {
        int v = que.top().to;
        que.pop();
        if (vis[v])
          continue;
        vis[v] = true;
        if (v == t)
          break;
        // dist[v] = shortest(s, v) + dual[s] - dual[v]
        // dist[v] >= 0 (all reduced cost are positive)
        // dist[v] <= (n-1)C
        for (int i = 0; i < (int)(g[v].size()); i++) {
          auto e = g[v][i];
          if (vis[e.to] || !e.cap)
            continue;
          // |-dual[e.to] + dual[v]| <= (n-1)C
          // cost <= C - -(n-1)C + 0 = nC
          Cost cost = e.cost - dual[e.to] + dual[v];
          if (dist[e.to] - dist[v] > cost) {
            dist[e.to] = dist[v] + cost;
            pv[e.to] = v;
            pe[e.to] = i;
            que.push(Q{dist[e.to], e.to});
          }
        }
      }
      if (!vis[t]) {
        return false;
      }

      for (int v = 0; v < _n; v++) {
        if (!vis[v])
          continue;
        // dual[v] = dual[v] - dist[t] + dist[v]
        //         = dual[v] - (shortest(s, t) + dual[s] - dual[t]) +
        //         (shortest(s, v) + dual[s] - dual[v]) = - shortest(s, t) +
        //         dual[t] + shortest(s, v) = shortest(s, v) - shortest(s, t) >=
        //         0 - (n-1)C
        dual[v] -= dist[t] - dist[v];
      }
      return true;
    };
    Cap flow = 0;
    Cost cost = 0, prev_cost = -1;
    std::vector<std::pair<Cap, Cost>> result;
    result.push_back({flow, cost});
    while (flow < flow_limit) {
      if (!dual_ref())
        break;
      Cap c = flow_limit - flow;
      for (int v = t; v != s; v = pv[v]) {
        c = std::min(c, g[pv[v]][pe[v]].cap);
      }
      for (int v = t; v != s; v = pv[v]) {
        auto &e = g[pv[v]][pe[v]];
        e.cap -= c;
        g[v][e.rev].cap += c;
      }
      Cost d = -dual[s];
      flow += c;
      cost += c * d;
      if (prev_cost == d) {
        result.pop_back();
      }
      result.push_back({flow, cost});
      prev_cost = cost;
    }
    return result;
  }

private:
  int _n;

  struct _edge {
    int to, rev;
    Cap cap;
    Cost cost;
  };

  std::vector<std::pair<int, int>> pos;
  std::vector<std::vector<_edge>> g;
};

const int maxn = 200007;

int n, m, cnt;
string s[57];
mcf_graph<int, int> dinic(3000);

void dfs(int i, int j, int ini, int inj) {
  dinic.add_edge(n * m + cnt + 1, i * m + j + 2, 1,
                 n + m - (i - ini + j - inj));
  //   cout<<cnt<<" "<<i<<" "<<j<<" "<<i-ini+j-inj<<endl;
  if (i + 1 < n && s[i + 1][j] != '#')
    dfs(i + 1, j, ini, inj);
  if (j + 1 < m && s[i][j + 1] != '#')
    dfs(i, j + 1, ini, inj);
}
signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> n >> m;
  cnt = 0;
  rep(i, n) cin >> s[i];
  rep(i, n) rep(j, m) {
    if (s[i][j] == 'o')
      ++cnt, dfs(i, j, i, j);
  }
  for (int i = 1; i <= cnt; ++i)
    dinic.add_edge(1, n * m + i + 1, 1, 0);
  rep(i, n) rep(j, m) dinic.add_edge(i * m + j + 2, n * m + cnt + 7, 1, 0);
  //   cout<<dinic.maxflow(1,n*m+cnt+7).first<<endl;
  cout << (n + m) * cnt - (dinic.flow(1, n * m + cnt + 7).second);
  return 0;
}

// #pragma GCC optimize("Ofast")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
// #pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
// #define int long long
using namespace std;

#define rep(i, n) for (int i = 0; i < (int)(n); ++i)
#define rep1(i, n) for (int i = 1; i <= (int)(n); ++i)
#define range(x) begin(x), end(x)
#define sz(x) (int)(x).size()
#define pb push_back
#define F first
#define S second

typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pii;
typedef vector<int> vi;
/*
计算u^v % mod
*/
ll power(ll u, ll v, ll mod) {
  ll ret = 1, t = u % mod;
  while (v) {
    if (v & 1)
      ret = t * ret % mod;
    t = t * t % mod, v >>= 1;
  }
  return ret % mod;
}

/*
计算gcd(a,b)并找寻ax+by=gcd(a,b)的一组解
ret=exgcd(a,b,x,y);
*/
ll exgcd(ll a, ll b, ll &x, ll &y) {
  if (b) {
    ll d = exgcd(b, a % b, y, x);
    return y -= a / b * x, d;
  }
  return x = 1, y = 0, a;
}

/*
计算x=a(mod m), x=b(mod n)的一组解，若无解返回-1
ret=crt(a,m,b,n);
*/
ll crt(ll a, ll m, ll b, ll n) {
  if (n > m)
    swap(a, b), swap(n, m);
  ll x, y, g = exgcd(m, n, x, y);
  if ((b - a) % g)
    return -1;
  x = (b - a) % n * x % n / g * m + a;
  return x < 0 ? x + m * n / g : x;
}

/*
生成<=n的质数
gen_prime<5000>(ret);
*/

void gen_prime(int n, vi &ret) {
  vector<bool> p;
  p.resize(n + 1, 1);
  p[0] = p[1] = 0;
  for (int i = 4; i <= n; i += 2)
    p[i] = 0;
  for (int i = 3; i * i <= n; i += 2) {
    if (p[i]) {
      for (int j = i * i; j <= n; j += 2 * i) {
        p[j] = 0;
      }
    }
  }
  for (int i = 2; i <= n; ++i) {
    if (p[i])
      ret.pb(i);
  }
}

/*
生成<=n的phi
gen_phi<5000>(ret);
*/
void gen_phi(int n, vi &phi) {
  phi.resize(n + 1);
  for (int i = 0; i <= n; ++i)
    phi[i] = i & 1 ? i : i / 2;
  for (int i = 3; i <= n; i += 2) {
    if (phi[i] == i) {
      for (int j = i; j <= n; j += i)
        phi[j] -= phi[j] / i;
    }
  }
}

/*
支持更高的快速幂操作
expower.mod_pow(a,b,mod);
*/

struct expower {
  ull mod_mul(ull a, ull b, ull M) {
    ll ret = a * b - M * ull(ld(a) * ld(b) / ld(M));
    return ret + M * (ret < 0) - M * (ret >= (ll)M);
  }
  ull mod_pow(ull b, ull e, ull mod) {
    ull ans = 1;
    for (; e; b = mod_mul(b, b, mod), e /= 2)
      if (e & 1)
        ans = mod_mul(ans, b, mod);
    return ans % mod;
  }
} expower;

/*
Miller-Rubin素数判别
is_prime(n);
*/
bool is_prime(ull n) {
  if (n < 2 || n % 6 % 4 != 1)
    return n - 2 < 2;
  ull A[] = {2, 325, 9375, 28178, 450775, 9780504, 1795265022},
      s = __builtin_ctzll(n - 1), d = n >> s;
  for (auto a : A) {
    ull p = expower.mod_pow(a, d, n), i = s;
    while (p != 1 && p != n - 1 && a % n && i--)
      p = expower.mod_mul(p, p, n);
    if (p != n - 1 && i != s)
      return 0;
  }
  return 1;
}

/*
素因数分解
ret=factorization(n);
*/
ull pollard(ull n) {
  auto f = [n](ull x) { return (expower.mod_mul(x, x, n) + 1) % n; };
  if (!(n & 1))
    return 2;
  for (ull i = 2;; i++) {
    ull x = i, y = f(x), p;
    while ((p = __gcd(n + y - x, n)) == 1)
      x = f(x), y = f(f(y));
    if (p != n)
      return p;
  }
}

vector<ull> factorization(ull n) {
  if (n == 1)
    return {};
  if (is_prime(n))
    return {n};
  ull x = pollard(n);
  auto l = factorization(x), r = factorization(n / x);
  l.insert(l.end(), range(r));
  return l;
}

struct DSU {
  // insert info.
  vector<int> p;
  DSU(int n) {
    p.resize(n + 1);
    for (int i = 1; i <= n; ++i)
      p[i] = i;
  }
  int find(int u) {
    if (p[u] == u)
      return u;
    return p[u] = find(find(p[u]));
  }
  bool unite(int u, int v) {
    int pu = find(u), pv = find(v);
    if (pu != pv) {
      p[pu] = pv;
    }
    return pu != pv;
  }
};

/* (u,v,w) */
template <class T>
vector<tuple<int, int, T>> MST(int n, vector<tuple<int, int, T>> g) {
  vector<tuple<int, int, T>> ret;
  ret.clear();
  DSU dsu(n);
  auto cmp = [&](tuple<int, int, T> x, tuple<int, int, T> y) {
    int ux, uy, vx, vy;
    T wx, wy;
    tie(ux, vx, wx) = x, tie(uy, vy, wy) = y;
    return wx < wy;
  };
  sort(range(g), cmp);
  for (auto c : g) {
    int u, v;
    T w;
    tie(u, v, w) = c;
    if (dsu.unite(u, v))
      ret.push_back(c);
  }
  return ret;
}

template <int n, class T> struct SegmentTree {
  typedef struct {
    T lz, mx, mn, sum;
  } Node;
  vector<Node> tree;
  SegmentTree() { tree.resize((n + 7) << 2); }
  void pushup(int num) {
    tree[num].mx = max(tree[num << 1].mx, tree[num << 1 | 1].mx);
    tree[num].mn = min(tree[num << 1].mn, tree[num << 1 | 1].mn);
    tree[num].sum = tree[num << 1].sum + tree[num << 1 | 1].sum;
  }
  void pushdown(int num, int l, int r, int md) {
    if (tree[num].lz) {
      tree[num << 1].mx += tree[num].lz;
      tree[num << 1 | 1].mx += tree[num].lz;
      tree[num << 1].mn += tree[num].lz;
      tree[num << 1 | 1].mn += tree[num].lz;
      tree[num << 1].sum += tree[num].lz * (md - l + 1);
      tree[num << 1 | 1].sum += tree[num].lz * (r - md);
      tree[num << 1].lz += tree[num].lz;
      tree[num << 1 | 1].lz += tree[num].lz;
      tree[num].lz = 0;
    }
  }
  void update1(int num, int l, int r, int L, int R, T val) {
    if (L <= l && r <= R) {
      tree[num].mx += val;
      tree[num].mn += val;
      tree[num].sum += val * (r - l + 1);
      tree[num].lz += val;
      return;
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    if (md >= L)
      update1(num << 1, l, md, L, R, val);
    if (md < R)
      update1(num << 1 | 1, md + 1, r, L, R, val);
    pushup(num);
  }
  tuple<T, T, T> query1(int num, int l, int r, int L, int R) {
    if (L <= l && r <= R) {
      return {tree[num].mn, tree[num].mx, tree[num].sum};
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    if (md >= R) {
      return query1(num << 1, l, md, L, R);
    } else {
      if (md < L) {
        return query1(num << 1 | 1, md + 1, r, L, R);
      } else {
        tuple<T, T, T> ret1 = query1(num << 1, l, md, L, R),
                       ret2 = query1(num << 1 | 1, md + 1, r, L, R);
        T a, b, c, d, e, f;
        tie(a, b, c) = ret1, tie(d, e, f) = ret2;
        return {min(a, d), max(b, e), c + f};
      }
    }
  }
  void update(int L, int R, int w) { update1(1, 1, n, L, R, w); }
  tuple<T, T, T> query(int L, int R) { return query1(1, 1, n, L, R); }
  void debug1(int num, int l, int r) {
    if (l == r) {
      cout << tree[num].mn << " ";
      return;
    }
    int md = (l + r) >> 1;
    pushdown(num, l, r, md);
    debug1(num << 1, l, md);
    debug1(num << 1 | 1, md + 1, r);
  }
  void debug() {
    debug1(1, 1, n);
    cout << endl;
  }
};

struct XORBasis {
  int n;
  vector<ull> p;
  vector<ull> ans;
  XORBasis(int k) : n(k) { p.resize(k + 1); }
  void rebuilt() {
    ans.clear();
    for (int i = 0; i <= n; ++i) {
      if (p[i])
        ans.push_back(p[i]);
    }
  }
  void insert(ull u) {
    for (int i = n; i >= 0; --i) {
      if (u & (1ull << i)) {
        if (p[i])
          u ^= p[i];
        else {
          p[i] = u;
          for (int j = i - 1; j >= 0; --j) {
            if (p[i] & (1ull << j))
              p[i] ^= p[j];
          }
          for (int j = i + 1; j <= n; ++j) {
            if (p[j] & (1ull << i))
              p[j] ^= p[i];
          }
          break;
        }
      }
    }
    rebuilt();
  }
  bool check(ull u) {
    for (int i = n; i > -1; --i) {
      if (u & (1ull << i))
        u ^= p[i];
    }
    return (u == 0);
  }
  ull kth(ull t) { // the k-th smallest, excluding 0.
    if (t >= (1ull << sz(ans)))
      return -1;
    ull ret = 0;
    for (int i = 0; i < sz(ans); ++i) {
      if (t & (1ull << i))
        ret ^= ans[i];
    }
    return ret;
  }
  ull total() { return (1ull << sz(ans)); }
};

vi pi(const string &s) {
  vi p(sz(s));
  for (int i = 1; i <= sz(s); ++i) {
    int g = p[i - 1];
    while (g && s[i] != s[g])
      g = p[g - 1];
    p[i] = g + (s[i] == s[g]);
  }
  return p;
}

vi match(const string &s, const string &pat) {
  vi p = pi(pat + '\\0' + s), res;
  for (int i = sz(p) - sz(s); i <= sz(p); ++i)
    if (p[i] == sz(pat))
      res.push_back(i - 2 * sz(pat));
  return res;
}

struct AhoCorasick {
  enum { alpha = 26, first = 'a' };
  struct Node {
    // (nmatches is optional)
    int back, next[alpha], start = -1, end = -1;
    ll nmatches = 0;
    Node(int v) { memset(next, v, sizeof(next)); }
  };
  vector<Node> N;
  vector<int> backp;
  void insert(string &s, int j, ll val) {
    assert(!s.empty());
    int n = 0;
    for (auto c : s) {
      int &m = N[n].next[c - first];
      if (m == -1) {
        n = m = sz(N);
        N.emplace_back(-1);
      } else
        n = m;
    }
    if (N[n].end == -1)
      N[n].start = j;
    backp.push_back(N[n].end);
    N[n].end = j;
    N[n].nmatches += val;
  }
  AhoCorasick(vector<string> &pat, vi val) {
    N.emplace_back(-1);
    for (int i = 0; i < sz(pat); ++i)
      insert(pat[i], i, val[i]);
    N[0].back = sz(N);
    N.emplace_back(0);

    queue<int> q;
    for (q.push(0); !q.empty(); q.pop()) {
      int n = q.front(), prev = N[n].back;
      for (int i = 0; i < alpha; ++i) {
        int &ed = N[n].next[i], y = N[prev].next[i];
        if (ed == -1)
          ed = y;
        else {
          N[ed].back = y;
          (N[ed].end == -1 ? N[ed].end : backp[N[ed].start]) = N[y].end;
          N[ed].nmatches += N[y].nmatches;
          q.push(ed);
        }
      }
    }
  }
  vi find(string &word, ll &ans) {
    int n = 0;
    vi res;
    for (auto c : word) {
      n = N[n].next[c - first];
      res.push_back(N[n].end);
      ans += N[n].nmatches;
    }
    return res;
  }
  vector<vi> findAll(vector<string> &pat, string word) {
    ll ans = 0;
    vi r = find(word, ans);
    vector<vi> res(sz(word));
    for (int i = 0; i < sz(word); ++i) {
      int ind = r[i];
      while (ind != -1) {
        res[i - sz(pat[ind]) + 1].push_back(ind);
        ind = backp[ind];
      }
    }
    return res;
  }
};

struct HLD {
  int n, cnt = 0;
  vector<vi> G;
  vi par, dep, siz, son, top, dfn, rk;
  // 父亲节点，深度，子树大小，重子节点，重链头部，dfs序，dfs序对应的节点编号
  HLD(vector<vi> g, int root = 1)
      : n(sz(g)), G(g), par(n + 7), dep(n + 7), siz(n + 7, 1), son(n + 7, -1),
        top(n + 7), dfn(n + 7), rk(n + 7) {
    dep[0] = -1, dfs1(root, 0), dfs2(root, 0, root);
  }
  void dfs1(int u, int p) {
    for (auto c : G[u]) {
      if (c == p)
        continue;
      dep[c] = dep[u] + 1, par[c] = u;
      dfs1(c, u);
      siz[u] += siz[c];
      if (son[u] == -1 || siz[son[u]] < siz[c])
        son[u] = c;
    }
  }
  void dfs2(int u, int p, int tp) {
    top[u] = tp;
    dfn[u] = ++cnt;
    rk[cnt] = u;
    if (son[u] == -1)
      return;
    dfs2(son[u], u, tp);
    for (auto c : G[u]) {
      if (c == p || c == son[u])
        continue;
      dfs2(c, u, c);
    }
  }
  int lca(int u, int v) {
    while (top[u] != top[v]) {
      if (dep[top[u]] > dep[top[v]]) {
        u = par[top[u]];
      } else {
        v = par[top[v]];
      }
    }
    return dep[u] > dep[v] ? v : u;
  }
  vector<pii> find_seg(int u, int v) {
    vector<pii> ft, bk, res;
    ft.clear(), bk.clear(), res.clear();
    //        cout<<top[u]<<" "<<top[v]<<endl;
    while (top[u] != top[v]) {
      if (dep[top[u]] > dep[top[v]]) {
        ft.pb({u, top[u]});
        u = par[top[u]];
      } else {
        bk.pb({top[v], v});
        v = par[top[v]];
      }
    }
    ft.pb({u, v});
    for (int i = 0; i < sz(ft); ++i)
      res.pb(ft[i]);
    for (int i = sz(bk); i > 0; --i)
      res.pb(bk[i - 1]);
    return res;
  }
};

struct BIT {
  vi C;
  int n;
  BIT(int n) : n(n) { C.resize(n + 7, 0); }
  void update(int u, int w) {
    for (; u <= n; u += u & (-u))
      C[u] += w;
  }
  int query(int u) {
    int res = 0;
    for (; u > 0; u -= u & (-u))
      res += C[u];
    return res;
  }
};

vi topoSort(const vector<vi> &gr) {
  vi indeg(sz(gr)), ret;
  for (auto &li : gr)
    for (int x : li)
      indeg[x]++;
  queue<int> q; // use priority queue for lexic. smallest ans.
  rep(i, sz(gr)) if (indeg[i] == 0) q.push(-i);
  while (!q.empty()) {
    int i = -q.front(); // top() for priority queue
    ret.push_back(i);
    q.pop();
    for (int x : gr[i])
      if (--indeg[x] == 0)
        q.push(-x);
  }
  return ret;
}

vi path;
namespace Eulerian {
const int N = 3, M = 3;
int t, n, m, tot, cc, hd[N], nxt[M], to[M], bh[M], vis[M], in[N], out[N],
    ans[M];
void add(int x, int y, int id) {
  nxt[++tot] = hd[x];
  hd[x] = tot;
  to[tot] = y;
  bh[tot] = id;
  in[y]++;
  out[x]++;
}
void dfs(int x) {
  for (int i = hd[x]; i; i = hd[x]) {
    while (i && vis[abs(bh[i])])
      i = nxt[i];
    hd[x] = i;
    if (!i)
      break;
    vis[abs(bh[i])] = 1;
    dfs(to[i]), ans[++cc] = bh[i];
  }
}

void init() {
  memset(hd, 0, sizeof(hd));
  memset(nxt, 0, sizeof(nxt));
  memset(to, 0, sizeof(to));
  memset(bh, 0, sizeof(bh));
  memset(vis, 0, sizeof(vis));
  memset(in, 0, sizeof(in));
  memset(out, 0, sizeof(out));
  memset(ans, 0, sizeof(ans));
  t = 1;
  n = 0;
  m = 0;
  tot = 0;
  cc = 0;
}
bool solve() {
  for (int i = 1; i <= n; i++)
    if ((t == 1 && in[i] & 1)) {
      return false;
    }
  dfs(to[1]);
  if (cc < m)
    return false;
  else {
    path.clear();
    for (int i = cc; i >= 1; i--)
      path.push_back(ans[i]);
    return true;
  }
}
} // namespace Eulerian

namespace FWT {
// mod=-1 (no mod)
const int mod = -1;

inline int modadd(int a, int b) {
  a += b;
  if (mod == -1)
    return a;
  a %= mod, a += mod, a %= mod;
  return a;
}

vi OR(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1)
        b[j] = modadd(b[j], b[j ^ (1 << i)] * mode);
    }
  }
  return b;
}
vi AND(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1)
        b[j ^ (1 << i)] = modadd(b[j ^ (1 << i)], b[j] * mode);
    }
  }
  return b;
}
vi XOR(vi &a, int mode = 1) {
  vi b(a);
  for (int i = 0; (1 << i) < sz(a); ++i) {
    for (int j = 0; j < sz(a); ++j) {
      if (j >> i & 1) {
        b[j ^ (1 << i)] = modadd(b[j ^ (1 << i)], b[j]);
        b[j] = modadd(b[j ^ (1 << i)], -2 * b[j]);
        if (mode < 0) {
          if (mod > 0)
            b[j] = b[j] * power(2, mod - 2, mod) % mod,
            b[j ^ (1 << i)] = b[j ^ (1 << i)] * power(2, mod - 2, mod) % mod;
          else
            b[j] /= 2, b[j ^ (1 << i)] /= 2;
        }
      }
    }
  }
  return b;
}
} // namespace FWT

/* atcoder mincost-maxflow */

template <class Cap, class Cost> struct mcf_graph {
public:
  mcf_graph() {}
  mcf_graph(int n) : _n(n), g(n) {}

  int add_edge(int from, int to, Cap cap, Cost cost) {
    assert(0 <= from && from < _n);
    assert(0 <= to && to < _n);
    int m = (int)(pos.size());
    pos.push_back({from, (int)(g[from].size())});
    g[from].push_back(_edge{to, (int)(g[to].size()), cap, cost});
    g[to].push_back(_edge{from, (int)(g[from].size()) - 1, 0, -cost});
    return m;
  }

  struct edge {
    int from, to;
    Cap cap, flow;
    Cost cost;
  };

  edge get_edge(int i) {
    int m = (int)(pos.size());
    assert(0 <= i && i < m);
    auto _e = g[pos[i].first][pos[i].second];
    auto _re = g[_e.to][_e.rev];
    return edge{
        pos[i].first, _e.to, _e.cap + _re.cap, _re.cap, _e.cost,
    };
  }
  std::vector<edge> edges() {
    int m = (int)(pos.size());
    std::vector<edge> result(m);
    for (int i = 0; i < m; i++) {
      result[i] = get_edge(i);
    }
    return result;
  }

  std::pair<Cap, Cost> flow(int s, int t) {
    return flow(s, t, std::numeric_limits<Cap>::max());
  }
  std::pair<Cap, Cost> flow(int s, int t, Cap flow_limit) {
    return slope(s, t, flow_limit).back();
  }
  std::vector<std::pair<Cap, Cost>> slope(int s, int t) {
    return slope(s, t, std::numeric_limits<Cap>::max());
  }
  std::vector<std::pair<Cap, Cost>> slope(int s, int t, Cap flow_limit) {
    assert(0 <= s && s < _n);
    assert(0 <= t && t < _n);
    assert(s != t);
    // variants (C = maxcost):
    // -(n-1)C <= dual[s] <= dual[i] <= dual[t] = 0
    // reduced cost (= e.cost + dual[e.from] - dual[e.to]) >= 0 for all edge
    std::vector<Cost> dual(_n, 0), dist(_n);
    std::vector<int> pv(_n), pe(_n);
    std::vector<bool> vis(_n);
    auto dual_ref = [&]() {
      std::fill(dist.begin(), dist.end(), std::numeric_limits<Cost>::max());
      std::fill(pv.begin(), pv.end(), -1);
      std::fill(pe.begin(), pe.end(), -1);
      std::fill(vis.begin(), vis.end(), false);
      struct Q {
        Cost key;
        int to;
        bool operator<(Q r) const { return key > r.key; }
      };
      std::priority_queue<Q> que;
      dist[s] = 0;
      que.push(Q{0, s});
      while (!que.empty()) {
        int v = que.top().to;
        que.pop();
        if (vis[v])
          continue;
        vis[v] = true;
        if (v == t)
          break;
        // dist[v] = shortest(s, v) + dual[s] - dual[v]
        // dist[v] >= 0 (all reduced cost are positive)
        // dist[v] <= (n-1)C
        for (int i = 0; i < (int)(g[v].size()); i++) {
          auto e = g[v][i];
          if (vis[e.to] || !e.cap)
            continue;
          // |-dual[e.to] + dual[v]| <= (n-1)C
          // cost <= C - -(n-1)C + 0 = nC
          Cost cost = e.cost - dual[e.to] + dual[v];
          if (dist[e.to] - dist[v] > cost) {
            dist[e.to] = dist[v] + cost;
            pv[e.to] = v;
            pe[e.to] = i;
            que.push(Q{dist[e.to], e.to});
          }
        }
      }
      if (!vis[t]) {
        return false;
      }

      for (int v = 0; v < _n; v++) {
        if (!vis[v])
          continue;
        // dual[v] = dual[v] - dist[t] + dist[v]
        //         = dual[v] - (shortest(s, t) + dual[s] - dual[t]) +
        //         (shortest(s, v) + dual[s] - dual[v]) = - shortest(s, t) +
        //         dual[t] + shortest(s, v) = shortest(s, v) - shortest(s, t) >=
        //         0 - (n-1)C
        dual[v] -= dist[t] - dist[v];
      }
      return true;
    };
    Cap flow = 0;
    Cost cost = 0, prev_cost = -1;
    std::vector<std::pair<Cap, Cost>> result;
    result.push_back({flow, cost});
    while (flow < flow_limit) {
      if (!dual_ref())
        break;
      Cap c = flow_limit - flow;
      for (int v = t; v != s; v = pv[v]) {
        c = std::min(c, g[pv[v]][pe[v]].cap);
      }
      for (int v = t; v != s; v = pv[v]) {
        auto &e = g[pv[v]][pe[v]];
        e.cap -= c;
        g[v][e.rev].cap += c;
      }
      Cost d = -dual[s];
      flow += c;
      cost += c * d;
      if (prev_cost == d) {
        result.pop_back();
      }
      result.push_back({flow, cost});
      prev_cost = cost;
    }
    return result;
  }

private:
  int _n;

  struct _edge {
    int to, rev;
    Cap cap;
    Cost cost;
  };

  std::vector<std::pair<int, int>> pos;
  std::vector<std::vector<_edge>> g;
};

const int maxn = 200007;

int n, m, cnt;
string s[57];
bool vis[57][57];
mcf_graph<int, int> dinic(3000);

void dfs(int i, int j, int ini, int inj) {
  if (vis[i][j])
    return;
  vis[i][j] = 1;
  dinic.add_edge(n * m + cnt + 1, i * m + j + 2, 1,
                 n + m - (i - ini + j - inj));
  //   cout<<cnt<<" "<<i<<" "<<j<<" "<<i-ini+j-inj<<endl;
  if (i + 1 < n && s[i + 1][j] != '#')
    dfs(i + 1, j, ini, inj);
  if (j + 1 < m && s[i][j + 1] != '#')
    dfs(i, j + 1, ini, inj);
}
signed main() {
  ios::sync_with_stdio(false);
  cin.tie(0), cout.tie(0);
  cin >> n >> m;
  cnt = 0;
  rep(i, n) cin >> s[i];
  rep(i, n) rep(j, m) {
    if (s[i][j] == 'o') {
      memset(vis, 0, sizeof(vis)), ++cnt, dfs(i, j, i, j);
    }
  }
  for (int i = 1; i <= cnt; ++i)
    dinic.add_edge(1, n * m + i + 1, 1, 0);
  rep(i, n) rep(j, m) dinic.add_edge(i * m + j + 2, n * m + cnt + 7, 1, 0);
  //   cout<<dinic.maxflow(1,n*m+cnt+7).first<<endl;
  cout << (n + m) * cnt - (dinic.flow(1, n * m + cnt + 7).second);
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
#define LOCAL
#pragma region Macros
typedef long long ll;
#define ALL(x) (x).begin(), (x).end()
const long long MOD = 1000000007;
// const long long MOD=998244353;
const int INF = 1e9;
const long long IINF = 1e18;
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const char dir[4] = {'D', 'R', 'U', 'L'};

template <typename T> istream &operator>>(istream &is, vector<T> &v) {
  for (T &x : v)
    is >> x;
  return is;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &v) {
  for (int i = 0; i < v.size(); ++i) {
    os << v[i] << (i + 1 == v.size() ? "" : " ");
  }
  return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
  os << '(' << p.first << ',' << p.second << ')';
  return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &m) {
  os << '{';
  for (auto itr = m.begin(); itr != m.end(); ++itr) {
    os << '(' << itr->first << ',' << itr->second << ')';
    if (++itr != m.end())
      os << ',';
    --itr;
  }
  os << '}';
  return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &s) {
  os << '{';
  for (auto itr = s.begin(); itr != s.end(); ++itr) {
    os << *itr;
    if (++itr != s.end())
      os << ',';
    --itr;
  }
  os << '}';
  return os;
}

void debug_out() { cerr << '\\n'; }
template <class Head, class... Tail>
void debug_out(Head &&head, Tail &&...tail) {
  cerr << head;
  if (sizeof...(Tail) > 0)
    cerr << ", ";
  debug_out(move(tail)...);
}
#ifdef LOCAL
#define debug(...)                                                             \\
  cerr << " ";                                                                 \\
  cerr << #__VA_ARGS__ << " :[" << __LINE__ << ":" << __FUNCTION__ << "]"      \\
       << '\\n';                                                                \\
  cerr << " ";                                                                 \\
  debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

template <typename T> T gcd(T x, T y) { return y != 0 ? gcd(y, x % y) : x; }
template <typename T> T lcm(T x, T y) { return x / gcd(x, y) * y; }

template <class T1, class T2> inline bool chmin(T1 &a, T2 b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
template <class T1, class T2> inline bool chmax(T1 &a, T2 b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
#pragma endregion

template <typename T, typename E> struct PrimalDual {
  const E inf = numeric_limits<E>::max();
  struct edge {
    int to, rev;
    T cap;
    E cost;
    edge(int to, T cap, E cost, int rev)
        : to(to), cap(cap), cost(cost), rev(rev) {}
  };
  vector<vector<edge>> G;
  vector<pair<int, int>> pos;
  vector<E> h, dist;
  vector<int> prevv, preve;
  PrimalDual(int n) : G(n), h(n), dist(n), prevv(n), preve(n) {}
  int add_edge(int from, int to, T cap, E cost) {
    pos.emplace_back(from, G[from].size());
    G[from].emplace_back(to, cap, cost, G[to].size());
    G[to].emplace_back(from, 0, -cost, G[from].size() - 1);
    return pos.size() - 1;
  }
  tuple<int, int, int, int, int> get_edge(int i) {
    auto e = G[pos[i].first][pos[i].second];
    auto re = G[e.to][e.rev];
    return {pos[i].first, e.to, e.cap + re.cap, re.cap, e.cost};
  }
  vector<tuple<int, int, int, int, int>> edges() {
    vector<tuple<int, int, int, int, int>> res;
    for (int i = 0; i < pos.size(); ++i) {
      res.emplace_back(get_edge(i));
    }
    return res;
  }
  void dijkstra(int s) {
    struct P {
      E c;
      int v;
      P(E c, int v) : c(c), v(v) {}
      bool operator<(const P &rhs) const { return c > rhs.c; }
    };
    priority_queue<P> pq;
    fill(dist.begin(), dist.end(), inf);
    dist[s] = 0;
    pq.emplace(dist[s], s);
    while (!pq.empty()) {
      auto p = pq.top();
      pq.pop();
      int v = p.v;
      if (dist[v] < p.c)
        continue;
      for (int i = 0; i < G[v].size(); ++i) {
        auto &e = G[v][i];
        if (e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
          dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
          prevv[e.to] = v;
          preve[e.to] = i;
          pq.emplace(dist[e.to], e.to);
        }
      }
    }
  }
  vector<pair<T, E>> slope(int s, int t, T lim) {
    T f = 0;
    E c = 0, pre = -1;
    vector<pair<T, E>> res;
    res.emplace_back(f, c);
    while (f < lim) {
      dijkstra(s);
      if (dist[t] == inf)
        break;
      for (int v = 0; v < G.size(); ++v)
        h[v] += dist[v];
      T d = lim - f;
      for (int v = t; v != s; v = prevv[v]) {
        d = min(d, G[prevv[v]][preve[v]].cap);
      }
      for (int v = t; v != s; v = prevv[v]) {
        auto &e = G[prevv[v]][preve[v]];
        e.cap -= d;
        G[v][e.rev].cap += d;
      }
      f += d;
      c += h[t] * d;
      if (pre == h[t])
        res.pop_back();
      res.emplace_back(f, c);
      pre = c;
    }
    return res;
  }
  E min_cost_flow(int s, int t, T f) {
    auto res = slope(s, t, f).back();
    return res.first == f ? res.second : -1;
  }
  pair<T, E> min_cost_max_flow(int s, int t) {
    return slope(s, t, numeric_limits<T>::max()).back();
  }
  vector<pair<T, E>> min_cost_slope(int s, int t) {
    return slope(s, t, numeric_limits<T>::max());
  }
};

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  int N, M;
  cin >> N >> M;
  vector<string> S(N);
  cin >> S;

  auto bfs = [&](int i, int j) {
    vector<vector<int>> res(N, vector<int>(M, INF));
    queue<pair<int, int>> que;
    que.emplace(i, j);
    res[i][j] = 0;
    while (!que.empty()) {
      auto p = que.front();
      que.pop();
      int x = p.first, y = p.second;
      for (int k = 0; k < 2; ++k) {
        int nx = x + dx[k], ny = y + dy[k];
        if (N <= nx || M <= ny)
          continue;
        if (S[nx][ny] == '#')
          continue;
        res[nx][ny] = res[x][y] + 1;
        que.emplace(nx, ny);
      }
    }
    return res;
  };

  PrimalDual<int, ll> PD(2 * N * M + 2);
  int s = 2 * N * M, t = s + 1, cnt = 0;
  auto id = [&](int x, int y) { return M * x + y; };
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
      if (S[i][j] != 'o')
        continue;
      ++cnt;
      PD.add_edge(s, id(i, j), 1, 0);
      vector<vector<int>> dist = bfs(i, j);
      for (int k = 0; k < N; ++k) {
        for (int l = 0; l < M; ++l) {
          if (dist[k][l] == INF)
            continue;
          PD.add_edge(id(i, j), N * M + id(k, l), 1, INF - dist[k][l]);
        }
      }
    }
  }

  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
      if (S[i][j] != '#') {
        PD.add_edge(N * M + id(i, j), t, 1, 0);
      }
    }
  }

  ll ans = PD.min_cost_flow(s, t, cnt);

  cout << (ll)INF * cnt - ans << '\\n';
}

#include <bits/stdc++.h>
using namespace std;
#define LOCAL
#pragma region Macros
typedef long long ll;
#define ALL(x) (x).begin(), (x).end()
const long long MOD = 1000000007;
// const long long MOD=998244353;
const int INF = 1e9;
const long long IINF = 1e18;
const int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};
const char dir[4] = {'D', 'R', 'U', 'L'};

template <typename T> istream &operator>>(istream &is, vector<T> &v) {
  for (T &x : v)
    is >> x;
  return is;
}
template <typename T> ostream &operator<<(ostream &os, const vector<T> &v) {
  for (int i = 0; i < v.size(); ++i) {
    os << v[i] << (i + 1 == v.size() ? "" : " ");
  }
  return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const pair<T, U> &p) {
  os << '(' << p.first << ',' << p.second << ')';
  return os;
}
template <typename T, typename U>
ostream &operator<<(ostream &os, const map<T, U> &m) {
  os << '{';
  for (auto itr = m.begin(); itr != m.end(); ++itr) {
    os << '(' << itr->first << ',' << itr->second << ')';
    if (++itr != m.end())
      os << ',';
    --itr;
  }
  os << '}';
  return os;
}
template <typename T> ostream &operator<<(ostream &os, const set<T> &s) {
  os << '{';
  for (auto itr = s.begin(); itr != s.end(); ++itr) {
    os << *itr;
    if (++itr != s.end())
      os << ',';
    --itr;
  }
  os << '}';
  return os;
}

void debug_out() { cerr << '\\n'; }
template <class Head, class... Tail>
void debug_out(Head &&head, Tail &&...tail) {
  cerr << head;
  if (sizeof...(Tail) > 0)
    cerr << ", ";
  debug_out(move(tail)...);
}
#ifdef LOCAL
#define debug(...)                                                             \\
  cerr << " ";                                                                 \\
  cerr << #__VA_ARGS__ << " :[" << __LINE__ << ":" << __FUNCTION__ << "]"      \\
       << '\\n';                                                                \\
  cerr << " ";                                                                 \\
  debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

template <typename T> T gcd(T x, T y) { return y != 0 ? gcd(y, x % y) : x; }
template <typename T> T lcm(T x, T y) { return x / gcd(x, y) * y; }

template <class T1, class T2> inline bool chmin(T1 &a, T2 b) {
  if (a > b) {
    a = b;
    return true;
  }
  return false;
}
template <class T1, class T2> inline bool chmax(T1 &a, T2 b) {
  if (a < b) {
    a = b;
    return true;
  }
  return false;
}
#pragma endregion

template <typename T, typename E> struct PrimalDual {
  const E inf = numeric_limits<E>::max();
  struct edge {
    int to, rev;
    T cap;
    E cost;
    edge(int to, T cap, E cost, int rev)
        : to(to), cap(cap), cost(cost), rev(rev) {}
  };
  vector<vector<edge>> G;
  vector<pair<int, int>> pos;
  vector<E> h, dist;
  vector<int> prevv, preve;
  PrimalDual(int n) : G(n), h(n), dist(n), prevv(n), preve(n) {}
  int add_edge(int from, int to, T cap, E cost) {
    pos.emplace_back(from, G[from].size());
    G[from].emplace_back(to, cap, cost, G[to].size());
    G[to].emplace_back(from, 0, -cost, G[from].size() - 1);
    return pos.size() - 1;
  }
  tuple<int, int, int, int, int> get_edge(int i) {
    auto e = G[pos[i].first][pos[i].second];
    auto re = G[e.to][e.rev];
    return {pos[i].first, e.to, e.cap + re.cap, re.cap, e.cost};
  }
  vector<tuple<int, int, int, int, int>> edges() {
    vector<tuple<int, int, int, int, int>> res;
    for (int i = 0; i < pos.size(); ++i) {
      res.emplace_back(get_edge(i));
    }
    return res;
  }
  void dijkstra(int s) {
    struct P {
      E c;
      int v;
      P(E c, int v) : c(c), v(v) {}
      bool operator<(const P &rhs) const { return c > rhs.c; }
    };
    priority_queue<P> pq;
    fill(dist.begin(), dist.end(), inf);
    dist[s] = 0;
    pq.emplace(dist[s], s);
    while (!pq.empty()) {
      auto p = pq.top();
      pq.pop();
      int v = p.v;
      if (dist[v] < p.c)
        continue;
      for (int i = 0; i < G[v].size(); ++i) {
        auto &e = G[v][i];
        if (e.cap > 0 && dist[e.to] > dist[v] + e.cost + h[v] - h[e.to]) {
          dist[e.to] = dist[v] + e.cost + h[v] - h[e.to];
          prevv[e.to] = v;
          preve[e.to] = i;
          pq.emplace(dist[e.to], e.to);
        }
      }
    }
  }
  vector<pair<T, E>> slope(int s, int t, T lim) {
    T f = 0;
    E c = 0, pre = -1;
    vector<pair<T, E>> res;
    res.emplace_back(f, c);
    while (f < lim) {
      dijkstra(s);
      if (dist[t] == inf)
        break;
      for (int v = 0; v < G.size(); ++v)
        h[v] += dist[v];
      T d = lim - f;
      for (int v = t; v != s; v = prevv[v]) {
        d = min(d, G[prevv[v]][preve[v]].cap);
      }
      for (int v = t; v != s; v = prevv[v]) {
        auto &e = G[prevv[v]][preve[v]];
        e.cap -= d;
        G[v][e.rev].cap += d;
      }
      f += d;
      c += h[t] * d;
      if (pre == h[t])
        res.pop_back();
      res.emplace_back(f, c);
      pre = c;
    }
    return res;
  }
  E min_cost_flow(int s, int t, T f) {
    auto res = slope(s, t, f).back();
    return res.first == f ? res.second : -1;
  }
  pair<T, E> min_cost_max_flow(int s, int t) {
    return slope(s, t, numeric_limits<T>::max()).back();
  }
  vector<pair<T, E>> min_cost_slope(int s, int t) {
    return slope(s, t, numeric_limits<T>::max());
  }
};

const int MAX = 110;

int main() {
  cin.tie(0);
  ios::sync_with_stdio(false);
  int N, M;
  cin >> N >> M;
  vector<string> S(N);
  for (int i = 0; i < N; ++i)
    cin >> S[i];

  int s = N * M, t = s + 1, cnt = 0, ans = 0;
  PrimalDual<int, int> PD(t + 1);
  auto id = [&](int x, int y) { return x * M + y; };
  auto calc = [&](int x, int y) { return (N - 1 - x) + (M - 1 - y); };
  for (int i = 0; i < N; ++i) {
    for (int j = 0; j < M; ++j) {
      if (S[i][j] == '#')
        continue;
      if (S[i][j] == 'o') {
        PD.add_edge(s, id(i, j), 1, 0);
        ++cnt;
        ans += calc(i, j);
      }
      PD.add_edge(id(i, j), t, 1, calc(i, j));
      for (int k = 0; k < 2; ++k) {
        int nx = i + k, ny = j + 1 - k;
        if (N <= nx || M <= ny)
          continue;
        if (S[nx][ny] == '#')
          continue;
        PD.add_edge(id(i, j), id(nx, ny), MAX, 0);
      }
    }
  }

  ans -= PD.min_cost_flow(s, t, cnt);

  cout << ans << '\\n';
}


// #pragma GCC optimize("O3")
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define FIO                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);
#define trav(x, a) for (auto &x : a)
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define mem(a, v) memset((a), (v), sizeof(a))
#define endl "\\n"
#define case(t) cout << "Case #" << (t) << ": "
#define reada(a, n)                                                            \\
  for (int _i = 0; _i < (n); _i++)                                             \\
  read(a[_i])
#define pii pair<int, int>
#define pll pair<long long, long long>
#define vii vector<pii>
#define vll vector<pll>
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef cc_hash_table<int, int, hash<int>> ht;
typedef tree<int, null_type, less<int>, rb_tree_tag,
             tree_order_statistics_node_update>
    oset;
const double pi = acos(-1);
const int mod = 1e9 + 7;
const int inf = 1e9 + 7;
const int N = 1e6 + 5;
const double eps = 1e-9;

template <class T> void read(T &x) { cin >> x; }
template <class X, class Y> void read(pair<X, Y> &a) {
  read(a.first), read(a.second);
}
template <class T> void read(vector<T> &x) { trav(y, x) read(y); }
template <class T, class... O> void read(T &x, O &...y) { read(x), read(y...); }

string to_string(const char &x) { return string(1, x); }
string to_string(const char *&x) { return (string)x; }
string to_string(const string &x) { return x; }
template <class T, class U> string to_string(const pair<T, U> &x) {
  return to_string(x.first) + " " + to_string(x.second);
}
template <class T> string to_string(const vector<T> &x) {
  string ret = "";
  bool f = 0;
  trav(y, x) ret += (!f ? "" : " ") + to_string(y), f = 1;
  return ret;
}
void print() { cout << endl; }
template <class T> void pr(const T &x) { cout << to_string(x); }
template <class T, class... O> void print(const T &x, const O &...y) {
  pr(x);
  if (sizeof...(y))
    pr(" ");
  print(y...);
}

struct minBPM {
  vector<ll> mi, s, t;
  vector<int> mt, id, vis;
  int n, m;
  minBPM(int n, int m)
      : n(n), m(m), mi(m + 1), s(m + 1), t(n + 1), mt(m + 1), id(m + 1),
        vis(m + 1) {}
  ll matching(vector<vector<ll>> &mat) {
    for (int i = 1, x, nx, y; i <= n; i++) {
      fill(vis.begin(), vis.end(), 0);
      fill(mi.begin(), mi.end(), 1e18);
      mt[x = 0] = i;
      do {
        vis[x] = 1, y = mt[x], nx = 0;
        ll d = 1e18;
        for (int j = 1; j <= m; j++) {
          if (vis[j])
            continue;
          ll v = mat[y][j] - s[j] - t[y];
          mi[j] = v < mi[j] ? id[j] = x, v : mi[j];
          d = mi[j] < d ? nx = j, mi[j] : d;
        }
        for (int j = 0; j <= m; mi[j++] -= d)
          if (vis[j])
            s[j] -= d, t[mt[j]] += d;
      } while (mt[x = nx] != 0);
      for (; x != 0; mt[x] = mt[id[x]], x = id[x])
        ;
    }
    return -s[0];
  }
};

vector<string> a;

int main() {
  FIO int n, m;
  read(n, m);
  a.resize(n);
  read(a);
  vector<vector<ll>> mat(n * m + 1, vector<ll>(n * m + 1, inf));
  int av = 0;
  for (int i = n - 1; ~i; i--) {
    for (int j = m - 1; ~j; j--) {
      if (a[i][j] != 'o')
        continue;
      int cnt = 0;
      if (i + 1 < n && a[i + 1][j] != '#')
        cnt++;
      if (j + 1 < m && a[i][j + 1] != '#')
        cnt++;
      if (cnt == 0) {
        a[i][j] = '#';
        continue;
      }
      av++;
      vector<int> vis(n * m, 0);
      deque<pii> pq;
      pq.push_back(mp(i, j));
      while (sz(pq)) {
        pii cur = pq.front();
        pq.pop_front();
        mat[i * m + j + 1][cur.st * m + cur.nd + 1] =
            -(abs(i - cur.st) + abs(j - cur.nd));
        if (cur.st + 1 < n && a[cur.st + 1][cur.nd] != '#' &&
            vis[(cur.st + 1) * m + cur.nd] == 0) {
          pq.push_back(mp(cur.st + 1, cur.nd));
          vis[(cur.st + 1) * m + cur.nd] = 1;
        }
        if (cur.nd + 1 < m && a[cur.st][cur.nd + 1] != '#' &&
            vis[cur.st * m + cur.nd + 1] == 0) {
          pq.push_back(mp(cur.st, cur.nd + 1));
          vis[cur.st * m + cur.nd + 1] = 1;
        }
      }
    }
  }
  // trav(x, a) print(x);
  // trav(x, mat) print(x);
  minBPM g(n * m, n * m);
  print((n * m - av) * 1ll * inf - g.matching(mat));
  return 0;
}

// #pragma GCC optimize("O3")
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
#define FIO                                                                    \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(0);
#define trav(x, a) for (auto &x : a)
#define sz(x) (int)(x).size()
#define all(x) (x).begin(), (x).end()
#define mem(a, v) memset((a), (v), sizeof(a))
#define endl "\\n"
#define case(t) cout << "Case #" << (t) << ": "
#define reada(a, n)                                                            \\
  for (int _i = 0; _i < (n); _i++)                                             \\
  read(a[_i])
#define pii pair<int, int>
#define pll pair<long long, long long>
#define vii vector<pii>
#define vll vector<pll>
#define vi vector<int>
#define vl vector<long long>
#define pb push_back
#define mp make_pair
#define st first
#define nd second
using namespace std;
using namespace __gnu_pbds;
typedef long long ll;
typedef cc_hash_table<int, int, hash<int>> ht;
typedef tree<int, null_type, less<int>, rb_tree_tag,
             tree_order_statistics_node_update>
    oset;
const double pi = acos(-1);
const int mod = 1e9 + 7;
const int inf = 1e9 + 7;
const int N = 1e6 + 5;
const double eps = 1e-9;

template <class T> void read(T &x) { cin >> x; }
template <class X, class Y> void read(pair<X, Y> &a) {
  read(a.first), read(a.second);
}
template <class T> void read(vector<T> &x) { trav(y, x) read(y); }
template <class T, class... O> void read(T &x, O &...y) { read(x), read(y...); }

string to_string(const char &x) { return string(1, x); }
string to_string(const char *&x) { return (string)x; }
string to_string(const string &x) { return x; }
template <class T, class U> string to_string(const pair<T, U> &x) {
  return to_string(x.first) + " " + to_string(x.second);
}
template <class T> string to_string(const vector<T> &x) {
  string ret = "";
  bool f = 0;
  trav(y, x) ret += (!f ? "" : " ") + to_string(y), f = 1;
  return ret;
}
void print() { cout << endl; }
template <class T> void pr(const T &x) { cout << to_string(x); }
template <class T, class... O> void print(const T &x, const O &...y) {
  pr(x);
  if (sizeof...(y))
    pr(" ");
  print(y...);
}

struct minBPM {
  vector<ll> mi, s, t;
  vector<int> mt, id, vis;
  int n, m;
  minBPM(int n, int m)
      : n(n), m(m), mi(m + 1), s(m + 1), t(n + 1), mt(m + 1), id(m + 1),
        vis(m + 1) {}
  ll matching(vector<vector<ll>> &mat) {
    for (int i = 1, x, nx, y; i <= n; i++) {
      fill(vis.begin(), vis.end(), 0);
      fill(mi.begin(), mi.end(), 1e18);
      mt[x = 0] = i;
      do {
        vis[x] = 1, y = mt[x], nx = 0;
        ll d = 1e18;
        for (int j = 1; j <= m; j++) {
          if (vis[j])
            continue;
          ll v = mat[y][j] - s[j] - t[y];
          mi[j] = v < mi[j] ? id[j] = x, v : mi[j];
          d = mi[j] < d ? nx = j, mi[j] : d;
        }
        for (int j = 0; j <= m; mi[j++] -= d)
          if (vis[j])
            s[j] -= d, t[mt[j]] += d;
      } while (mt[x = nx] != 0);
      for (; x != 0; mt[x] = mt[id[x]], x = id[x])
        ;
    }
    return -s[0];
  }
};

vector<string> a;

int main() {
  FIO int n, m;
  read(n, m);
  a.resize(n);
  read(a);
  vector<vector<ll>> mat;
  {
    vector<ll> tmp(n * m + 1, inf);
    mat.push_back(tmp);
  }
  int av = 0;
  for (int i = n - 1; ~i; i--) {
    for (int j = m - 1; ~j; j--) {
      if (a[i][j] != 'o')
        continue;
      int cnt = 0;
      if (i + 1 < n && a[i + 1][j] != '#')
        cnt++;
      if (j + 1 < m && a[i][j + 1] != '#')
        cnt++;
      if (cnt == 0) {
        a[i][j] = '#';
        continue;
      }
      av++;
      vector<int> vis(n * m, 0);
      deque<pii> pq;
      pq.push_back(mp(i, j));
      vector<ll> tmp(n * m + 1, inf);
      while (sz(pq)) {
        pii cur = pq.front();
        pq.pop_front();
        tmp[cur.st * m + cur.nd + 1] = -(abs(i - cur.st) + abs(j - cur.nd));
        if (cur.st + 1 < n && a[cur.st + 1][cur.nd] != '#' &&
            vis[(cur.st + 1) * m + cur.nd] == 0) {
          pq.push_back(mp(cur.st + 1, cur.nd));
          vis[(cur.st + 1) * m + cur.nd] = 1;
        }
        if (cur.nd + 1 < m && a[cur.st][cur.nd + 1] != '#' &&
            vis[cur.st * m + cur.nd + 1] == 0) {
          pq.push_back(mp(cur.st, cur.nd + 1));
          vis[cur.st * m + cur.nd + 1] = 1;
        }
      }
      mat.push_back(tmp);
    }
  }
  // trav(x, a) print(x);
  // trav(x, mat) print(x);
  minBPM g(av, n * m);
  print(-g.matching(mat));
  return 0;
}


#include <bits/stdc++.h>
using namespace std;
// #pragma GCC optimize("Ofast")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

#define ms(s, n) memset(s, n, sizeof(s))
#define FOR(i, a, b) for (int i = (a); i < (b); ++i)
#define FORd(i, a, b) for (int i = (a)-1; i >= (b); --i)
#define FORall(it, a)                                                          \\
  for (__typeof((a).begin()) it = (a).begin(); it != (a).end(); it++)
#define sz(a) int((a).size())
#define present(t, x) (t.find(x) != t.end())
#define all(a) (a).begin(), (a).end()
#define uni(a) (a).erase(unique(all(a)), (a).end())
#define pb push_back
#define pf push_front
#define mp make_pair
#define fi first
#define se second
#define prec(n) fixed << setprecision(n)
#define bit(n, i) (((n) >> (i)) & 1)
#define bitcount(n) __builtin_popcountll(n)
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pi;
typedef vector<int> vi;
typedef vector<pi> vii;
const int MOD = (int)1e9 + 7;
const int FFTMOD = 119 << 23 | 1;
const int INF = (int)1e9 + 23111992;
const ll LINF = (ll)1e18 + 23111992;
const ld PI = acos((ld)-1);
const ld EPS = 1e-9;
inline ll gcd(ll a, ll b) {
  ll r;
  while (b) {
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}
inline ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }
inline ll fpow(ll n, ll k, int p = MOD) {
  ll r = 1;
  for (; k; k >>= 1) {
    if (k & 1)
      r = r * n % p;
    n = n * n % p;
  }
  return r;
}
template <class T> inline int chkmin(T &a, const T &val) {
  return val < a ? a = val, 1 : 0;
}
template <class T> inline int chkmax(T &a, const T &val) {
  return a < val ? a = val, 1 : 0;
}
inline ull isqrt(ull k) {
  ull r = sqrt(k) + 1;
  while (r * r > k)
    r--;
  return r;
}
inline ll icbrt(ll k) {
  ll r = cbrt(k) + 1;
  while (r * r * r > k)
    r--;
  return r;
}
inline void addmod(int &a, int val, int p = MOD) {
  if ((a = (a + val)) >= p)
    a -= p;
}
inline void submod(int &a, int val, int p = MOD) {
  if ((a = (a - val)) < 0)
    a += p;
}
inline int mult(int a, int b, int p = MOD) { return (ll)a * b % p; }
inline int inv(int a, int p = MOD) { return fpow(a, p - 2, p); }
inline int sign(ld x) { return x < -EPS ? -1 : x > +EPS; }
inline int sign(ld x, ld y) { return sign(x - y); }
mt19937 mt(chrono::high_resolution_clock::now().time_since_epoch().count());
inline int mrand() { return abs((int)mt()); }
inline int mrand(int k) { return abs((int)mt()) % k; }
#define db(x) cerr << "[" << #x << ": " << (x) << "] ";
#define endln cerr << "\\n";

#define MF MincostMaxflow
#define flow_t int
#define cost_t long long
const flow_t foo = (flow_t)1e9;
const cost_t coo = (cost_t)1e18;
namespace MincostMaxflow {
const int maxv = 2e6 + 5;
const int maxe = 2e6 + 5;
int n, s, t, E;
int adj[maxe], nxt[maxe], lst[maxv], frm[maxv], vis[maxv];
flow_t cap[maxe], flw[maxe], totalFlow;
cost_t cst[maxe], dst[maxv], totalCost;

void init(int nn, int ss, int tt) {
  n = nn, s = ss, t = tt;
  fill_n(lst, n, -1), E = 0;
}
void add(int u, int v, flow_t ca, cost_t co) {
  adj[E] = v, cap[E] = ca, flw[E] = 0, cst[E] = +co, nxt[E] = lst[u],
  lst[u] = E++;
  adj[E] = u, cap[E] = 0, flw[E] = 0, cst[E] = -co, nxt[E] = lst[v],
  lst[v] = E++;
}
int spfa() {
  fill_n(dst, n, coo), dst[s] = 0;
  queue<int> que;
  que.push(s);
  while (que.size()) {
    int u = que.front();
    que.pop();
    for (int e = lst[u]; e != -1; e = nxt[e])
      if (flw[e] < cap[e]) {
        int v = adj[e];
        if (dst[v] > dst[u] + cst[e]) {
          dst[v] = dst[u] + cst[e];
          frm[v] = e;
          if (!vis[v]) {
            vis[v] = 1;
            que.push(v);
          }
        }
      }
    vis[u] = 0;
  }
  return dst[t] < coo;
}
cost_t mincost() {
  totalCost = 0, totalFlow = 0;
  while (1) {
    if (!spfa())
      break;
    flow_t mn = foo;
    for (int v = t, e = frm[v]; v != s; v = adj[e ^ 1], e = frm[v])
      mn = min(mn, cap[e] - flw[e]);
    for (int v = t, e = frm[v]; v != s; v = adj[e ^ 1], e = frm[v]) {
      flw[e] += mn;
      flw[e ^ 1] -= mn;
    }
    totalFlow += mn;
    totalCost += mn * dst[t];
  }
  return totalCost;
}
} // namespace MincostMaxflow

void chemthan() {
  int n, m;
  cin >> n >> m;
  vector<string> s(n);
  FOR(i, 0, n) cin >> s[i];
  int tot = n * m * 2;
  int S = tot++, t = tot++;
  MF::init(tot, S, t);
  int num = 0;
  FOR(i, 0, n) FOR(j, 0, m) if (s[i][j] == 'o') {
    MF::add(S, i * m + j, 1, 0);
    num++;
    vector<vi> dp(n, vi(m, INF));
    queue<int> que;
    dp[i][j] = 0, que.push(i), que.push(j);
    while (sz(que)) {
      int u = que.front();
      que.pop();
      int v = que.front();
      que.pop();
      if (u + 1 < n && s[u + 1][v] != '#') {
        if (chkmin(dp[u + 1][v], dp[u][v] + 1)) {
          que.push(u + 1), que.push(v);
        }
      }
      if (v + 1 < n && s[u][v + 1] != '#') {
        if (chkmin(dp[u][v + 1], dp[u][v] + 1)) {
          que.push(u), que.push(v + 1);
        }
      }
    }
    vector<tuple<int, int, int>> vals;
    FOR(u, 0, n) FOR(v, 0, m) if (dp[u][v] < INF) {
      MF::add(i * m + j, n * m + u * m + v, 1, 100000 - dp[u][v]);
    }
  }
  FOR(i, 0, n * m) { MF::add(n * m + i, t, 1, 0); }
  cout << (long long)100000 * num - MF::mincost() << "\\n";
}

int32_t main(int32_t argc, char *argv[]) {
  ios_base::sync_with_stdio(0), cin.tie(0);
  if (argc > 1) {
    assert(freopen(argv[1], "r", stdin));
  }
  if (argc > 2) {
    assert(freopen(argv[2], "wb", stdout));
  }
  chemthan();
  cerr << "\\nTime elapsed: " << 1000 * clock() / CLOCKS_PER_SEC << "ms\\n";
  return 0;
}

#include <bits/stdc++.h>
using namespace std;
// #pragma GCC optimize("Ofast")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")

#define ms(s, n) memset(s, n, sizeof(s))
#define FOR(i, a, b) for (int i = (a); i < (b); ++i)
#define FORd(i, a, b) for (int i = (a)-1; i >= (b); --i)
#define FORall(it, a)                                                          \\
  for (__typeof((a).begin()) it = (a).begin(); it != (a).end(); it++)
#define sz(a) int((a).size())
#define present(t, x) (t.find(x) != t.end())
#define all(a) (a).begin(), (a).end()
#define uni(a) (a).erase(unique(all(a)), (a).end())
#define pb push_back
#define pf push_front
#define mp make_pair
#define fi first
#define se second
#define prec(n) fixed << setprecision(n)
#define bit(n, i) (((n) >> (i)) & 1)
#define bitcount(n) __builtin_popcountll(n)
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
typedef pair<int, int> pi;
typedef vector<int> vi;
typedef vector<pi> vii;
const int MOD = (int)1e9 + 7;
const int FFTMOD = 119 << 23 | 1;
const int INF = (int)1e9 + 23111992;
const ll LINF = (ll)1e18 + 23111992;
const ld PI = acos((ld)-1);
const ld EPS = 1e-9;
inline ll gcd(ll a, ll b) {
  ll r;
  while (b) {
    r = a % b;
    a = b;
    b = r;
  }
  return a;
}
inline ll lcm(ll a, ll b) { return a / gcd(a, b) * b; }
inline ll fpow(ll n, ll k, int p = MOD) {
  ll r = 1;
  for (; k; k >>= 1) {
    if (k & 1)
      r = r * n % p;
    n = n * n % p;
  }
  return r;
}
template <class T> inline int chkmin(T &a, const T &val) {
  return val < a ? a = val, 1 : 0;
}
template <class T> inline int chkmax(T &a, const T &val) {
  return a < val ? a = val, 1 : 0;
}
inline ull isqrt(ull k) {
  ull r = sqrt(k) + 1;
  while (r * r > k)
    r--;
  return r;
}
inline ll icbrt(ll k) {
  ll r = cbrt(k) + 1;
  while (r * r * r > k)
    r--;
  return r;
}
inline void addmod(int &a, int val, int p = MOD) {
  if ((a = (a + val)) >= p)
    a -= p;
}
inline void submod(int &a, int val, int p = MOD) {
  if ((a = (a - val)) < 0)
    a += p;
}
inline int mult(int a, int b, int p = MOD) { return (ll)a * b % p; }
inline int inv(int a, int p = MOD) { return fpow(a, p - 2, p); }
inline int sign(ld x) { return x < -EPS ? -1 : x > +EPS; }
inline int sign(ld x, ld y) { return sign(x - y); }
mt19937 mt(chrono::high_resolution_clock::now().time_since_epoch().count());
inline int mrand() { return abs((int)mt()); }
inline int mrand(int k) { return abs((int)mt()) % k; }
#define db(x) cerr << "[" << #x << ": " << (x) << "] ";
#define endln cerr << "\\n";

#define MF MincostMaxflow
#define flow_t int
#define cost_t long long
const flow_t foo = (flow_t)1e9;
const cost_t coo = (cost_t)1e18;
namespace MincostMaxflow {
const int maxv = 2e6 + 5;
const int maxe = 2e6 + 5;
int n, s, t, E;
int adj[maxe], nxt[maxe], lst[maxv], frm[maxv], vis[maxv];
flow_t cap[maxe], flw[maxe], totalFlow;
cost_t cst[maxe], dst[maxv], totalCost;

void init(int nn, int ss, int tt) {
  n = nn, s = ss, t = tt;
  fill_n(lst, n, -1), E = 0;
}
void add(int u, int v, flow_t ca, cost_t co) {
  adj[E] = v, cap[E] = ca, flw[E] = 0, cst[E] = +co, nxt[E] = lst[u],
  lst[u] = E++;
  adj[E] = u, cap[E] = 0, flw[E] = 0, cst[E] = -co, nxt[E] = lst[v],
  lst[v] = E++;
}
int spfa() {
  fill_n(dst, n, coo), dst[s] = 0;
  queue<int> que;
  que.push(s);
  while (que.size()) {
    int u = que.front();
    que.pop();
    for (int e = lst[u]; e != -1; e = nxt[e])
      if (flw[e] < cap[e]) {
        int v = adj[e];
        if (dst[v] > dst[u] + cst[e]) {
          dst[v] = dst[u] + cst[e];
          frm[v] = e;
          if (!vis[v]) {
            vis[v] = 1;
            que.push(v);
          }
        }
      }
    vis[u] = 0;
  }
  return dst[t] < coo;
}
cost_t mincost() {
  totalCost = 0, totalFlow = 0;
  while (1) {
    if (!spfa())
      break;
    flow_t mn = foo;
    for (int v = t, e = frm[v]; v != s; v = adj[e ^ 1], e = frm[v])
      mn = min(mn, cap[e] - flw[e]);
    for (int v = t, e = frm[v]; v != s; v = adj[e ^ 1], e = frm[v]) {
      flw[e] += mn;
      flw[e ^ 1] -= mn;
    }
    totalFlow += mn;
    totalCost += mn * dst[t];
  }
  return totalCost;
}
} // namespace MincostMaxflow

void chemthan() {
  int n, m;
  cin >> n >> m;
  vector<string> s(n);
  FOR(i, 0, n) cin >> s[i];
  int tot = n * m * 2;
  int S = tot++, t = tot++;
  MF::init(tot, S, t);
  int num = 0;
  FOR(i, 0, n) FOR(j, 0, m) if (s[i][j] == 'o') {
    MF::add(S, i * m + j, 1, 0);
    num++;
    vector<vi> dp(n, vi(m, INF));
    queue<int> que;
    dp[i][j] = 0, que.push(i), que.push(j);
    while (sz(que)) {
      int u = que.front();
      que.pop();
      int v = que.front();
      que.pop();
      if (u + 1 < n && s[u + 1][v] != '#') {
        if (chkmin(dp[u + 1][v], dp[u][v] + 1)) {
          que.push(u + 1), que.push(v);
        }
      }
      if (v + 1 < m && s[u][v + 1] != '#') {
        if (chkmin(dp[u][v + 1], dp[u][v] + 1)) {
          que.push(u), que.push(v + 1);
        }
      }
    }
    vector<tuple<int, int, int>> vals;
    FOR(u, 0, n) FOR(v, 0, m) if (dp[u][v] < INF) {
      MF::add(i * m + j, n * m + u * m + v, 1, 100000 - dp[u][v]);
    }
  }
  FOR(i, 0, n * m) { MF::add(n * m + i, t, 1, 0); }
  cout << (long long)100000 * num - MF::mincost() << "\\n";
}

int32_t main(int32_t argc, char *argv[]) {
  ios_base::sync_with_stdio(0), cin.tie(0);
  if (argc > 1) {
    assert(freopen(argv[1], "r", stdin));
  }
  if (argc > 2) {
    assert(freopen(argv[2], "wb", stdout));
  }
  chemthan();
  cerr << "\\nTime elapsed: " << 1000 * clock() / CLOCKS_PER_SEC << "ms\\n";
  return 0;
}


#include <bits/stdc++.h>

using namespace std;

template <class T> void ckmin(T &a, T b) { a = min(a, b); }
template <class T> void ckmax(T &a, T b) { a = max(a, b); }
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

#define pb push_back
#define mp make_pair
#define cotu cout
#define itn int
#define Red                                                                    \\
  ios_base::sync_with_stdio(0);                                                \\
  cin.tie(0);                                                                  \\
  cout.tie(0)
#define F first
#define S second
#define sz(x) (int)x.size()
#define all(x) (x).begin(), (x).end()
#define rep(i, n) for (int i = 0; i < n; ++i)
#define repr(i, n) for (int i = n - 1; i >= 0; --i)
#define Rep(i, a, n) for (int i = (a); i <= (n); ++i)
#define repst(i, n) for (auto it = n.begin(); it != n.end(); ++it)
#define Repr(i, a, n) for (int i = (n); i >= (a); --i)
#define setp(x) fixed << setprecision(x)
#define ordered_set                                                            \\
  tree<int, null_type, less<int>, rb_tree_tag,                                 \\
       tree_order_statistics_node_update>

#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;

const int inf = int(1e9);
const int N = 1e6 + 555; // TL MB
const double PI = acos(-1.0);
// void mul(ll &x, ll y){
// 	x *= y;
// 	if(x >= mod) x %= mod;
// }
// ll bp(ll a, ll n){
// 	ll r = 1;
// 	while(n){
// 		if(n & 1) mul(r, a);
// 		mul(a, a);
// 		n >>= 1;
// 	}
// 	return r;
// }
template <typename T_vector> // neal
void output_vector(const T_vector &v, bool add_one = false, int start = -1,
                   int end = -1) {
  if (start < 0)
    start = 0;
  if (end < 0)
    end = int(v.size());

  for (int i = start; i < end; i++)
    cout << v[i] + (add_one ? 1 : 0) << (i < end - 1 ? ' ' : '\\n');
}

struct Edge {
  int from, to, capacity, cost;
  Edge() {}
  Edge(int from, int to, int capacity, int cost)
      : from(from), to(to), capacity(capacity), cost(cost) {}
};

vector<vector<int>> adj, cost, capacity;

const int INF = 1e9;

int n, m;
void shortest_paths(int n, int v0, vector<int> &d, vector<int> &p) {
  d.assign(n, INF);
  d[v0] = 0;
  vector<bool> inq(n, false);
  queue<int> q;
  q.push(v0);
  p.assign(n, -1);

  while (!q.empty()) {
    int u = q.front();
    q.pop();
    inq[u] = false;
    for (int v : adj[u]) {
      if (capacity[u][v] > 0 && d[v] > d[u] + cost[u][v]) {
        d[v] = d[u] + cost[u][v];
        p[v] = u;
        if (!inq[v]) {
          inq[v] = true;
          q.push(v);
        }
      }
    }
  }
}

int min_cost_flow(int N, vector<Edge> edges, int K, int s, int t) {
  adj.assign(N, vector<int>());
  cost.assign(N, vector<int>(n * m + 1, 0));
  capacity.assign(N, vector<int>(n * m + 1, 0));
  for (Edge e : edges) {
    adj[e.from].push_back(e.to);
    adj[e.to].push_back(e.from);
    cost[e.from][e.to] = e.cost;
    cost[e.to][e.from] = -e.cost;
    capacity[e.from][e.to] = e.capacity;
  }
  int flow = 0;
  int cost = 0;
  vector<int> d, p;
  while (flow < K) {
    shortest_paths(N, s, d, p);
    if (d[t] == INF)
      break;

    // find max flow on that path
    int f = K - flow;
    int cur = t;
    while (cur != s) {
      f = min(f, capacity[p[cur]][cur]);
      cur = p[cur];
    }

    // apply flow
    flow += f;
    cost += f * d[t];
    cur = t;
    while (cur != s) {
      capacity[p[cur]][cur] -= f;
      capacity[cur][p[cur]] += f;
      cur = p[cur];
    }
  }

  return cost;
}
void solve() {
  cin >> n >> m;
  int sink = 0, flow = n * m + n * m + 1;
  char c[n][m];
  vector<Edge> v;
  rep(i, n) rep(j, m) { cin >> c[i][j]; }
  for (int i = 0; i < n; ++i) {
    for (int j = 0; j < m; ++j) {
      if (c[i][j] == 'o') {
        v.pb(Edge(sink, i * m + j + 1, 1, 0));
      } else if (c[i][j] == '.') {
        v.pb(Edge(n * m + i * m + j + 1, flow, 1, 0));
      }
    }
  }
  rep(i, n) {
    rep(j, m) {
      if (c[i][j] != 'o')
        continue;
      int id = i * m + j + 1;
      vector<vector<bool>> u(n, vector<bool>(m));
      queue<pair<int, pii>> q;
      q.push({i, {j, 0}});
      u[i][j] = 1;
      while (!q.empty()) {
        pair<int, pii> p = q.front();
        q.pop();
        int curid = n * m + p.S.F + p.F * m + 1;
        Edge e1(id, curid, 1, -p.S.S);
        v.pb(e1);
        if (p.S.F + 1 < m and !u[p.F][p.S.F + 1] and c[p.F][p.S.F + 1] != '#') {
          u[p.F][p.S.F + 1] = 1;
          q.push({p.F, {p.S.F + 1, p.S.S + 1}});
        }
        if (p.F + 1 < n and !u[p.F + 1][p.S.F] and c[p.F + 1][p.S.F] != '#') {
          u[p.F + 1][p.S.F] = 1;
          q.push({p.F + 1, {p.S.F, p.S.S + 1}});
        }
      }
    }
  }
  cout << -min_cost_flow(n * m + n * m + 2, v, n * m * n * m, sink, flow);
}

int main() {
  // freopen("input.txt", "r", stdin);
  // freopen("output.txt", "w", stdout);
  // Red;
  int T = 1;
  // cin >> T;
  for (int i = 1; i <= T; ++i) {
    solve();
  }
  return 0;
}

#include <bits/stdc++.h>

using namespace std;

template <class T> void ckmin(T &a, T b) { a = min(a, b); }
template <class T> void ckmax(T &a, T b) { a = max(a, b); }
mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());

#define pb push_back
#define mp make_pair
#define cotu cout
#define itn int
#define Red                                                                    \\
  ios_base::sync_with_stdio(0);                                                \\
  cin.tie(0);                                                                  \\
  cout.tie(0)
#define F first
#define S second
#define sz(x) (int)x.size()
#define all(x) (x).begin(), (x).end()
#define rep(i, n) for (int i = 0; i < n; ++i)
#define repr(i, n) for (int i = n - 1; i >= 0; --i)
#define Rep(i, a, n) for (int i = (a); i <= (n); ++i)
#define repst(i, n) for (auto it = n.begin(); it != n.end(); ++it)
#define Repr(i, a, n) for (int i = (n); i >= (a); --i)
#define setp(x) fixed << setprecision(x)
#define ordered_set                                                            \\
  tree<int, null_type, less<int>, rb_tree_tag,                                 \\
       tree_order_statistics_node_update>

#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>
using namespace __gnu_pbds;

typedef long long ll;
typedef pair<ll, ll> pll;
typedef pair<int, int> pii;

const int inf = int(1e9);
const int N = 1e6 + 555; // TL MB
const double PI = acos(-1.0);
// void mul(ll &x, ll y){
// 	x *= y;
// 	if(x >= mod) x %= mod;
// }
// ll bp(ll a, ll n){
// 	ll r = 1;
// 	while(n){
// 		if(n & 1) mul(r, a);
// 		mul(a, a);
// 		n >>= 1;
// 	}
// 	return r;
// }
template <typename T_vector> // neal
void output_vector(const T_vector &v, bool add_one = false, int start = -1,
                   int end = -1) {
  if (start < 0)
    start = 0;
  if (end < 0)
    end = int(v.size());

  for (int i = start; i < end; i++)
    cout << v[i] + (add_one ? 1 : 0) << (i < end - 1 ? ' ' : '\\n');
}

struct Edge {
  int from, to, capacity, cost;
  Edge() {}
  Edge(int from, int to, int capacity, int cost)
      : from(from), to(to), capacity(capacity), cost(cost) {}
};

vector<vector<int>> adj, cost, capacity;

const int INF = 1e9;

int n, m;
void shortest_paths(int n, int v0, vector<int> &d, vector<int> &p) {
  d.assign(n, INF);
  d[v0] = 0;
  vector<bool> inq(n, false);
  queue<int> q;
  q.push(v0);
  p.assign(n, -1);

  while (!q.empty()) {
    int u = q.front();
    q.pop();
    inq[u] = false;
    for (int v : adj[u]) {
      if (capacity[u][v] > 0 && d[v] > d[u] + cost[u][v]) {
        d[v] = d[u] + cost[u][v];
        p[v] = u;
        if (!inq[v]) {
          inq[v] = true;
          q.push(v);
        }
      }
    }
  }
}

int min_cost_flow(int N, vector<Edge> edges, int K, int s, int t) {
  adj.assign(N, vector<int>());
  cost.assign(N, vector<int>(N, 0));
  capacity.assign(N, vector<int>(N, 0));
  for (Edge e : edges) {
    adj[e.from].push_back(e.to);
    adj[e.to].push_back(e.from);
    cost[e.from][e.to] = e.cost;
    cost[e.to][e.from] = -e.cost;
    capacity[e.from][e.to] = e.capacity;
  }
  int flow = 0;
  int cost = 0;
  vector<int> d, p;
  while (flow < K) {
    shortest_paths(N, s, d, p);
    if (d[t] == INF)
      break;

    // find max flow on that path
    int f = K - flow;
    int cur = t;
    while (cur != s) {
      f = min(f, capacity[p[cur]][cur]);
      cur = p[cur];
    }

    // apply flow
    flow += f;
    cost += f * d[t];
    cur = t;
    while (cur != s) {
      capacity[p[cur]][cur] -= f;
      capacity[cur][p[cur]] += f;
      cur = p[cur];
    }
  }

  return cost;
}
void solve() {
  cin >> n >> m;
  int sink = 0, flow = n * m + n + m;
  char c[n][m];
  vector<Edge> v;
  rep(i, n) rep(j, m) { cin >> c[i][j]; }
  rep(i, n) {
    rep(j, m) {
      if (c[i][j] == 'o') {
        v.pb(Edge(sink, i * m + j + 1, 1, 0));
      }
    }
  }
  rep(i, n) {
    rep(j, m) {
      if (c[i][j] != 'o')
        continue;
      int id = i * m + j + 1;
      vector<vector<int>> u(n, vector<int>(m));
      queue<pair<int, pii>> q;
      q.push({i, {j, 0}});
      u[i][j] = 1;
      while (!q.empty()) {
        pair<int, pii> p = q.front();
        q.pop();
        int curid = p.S.F + p.F * m + 1;
        Edge e1(id, curid, 1, -p.S.S);
        // Edge e2(curid, id, 1, p.S.S);
        v.pb(e1); // v.pb(e2);
        if (p.S.F + 1 < m and p.S.S + 1 > u[p.F][p.S.F + 1] and
            c[p.F][p.S.F + 1] != '#') {
          u[p.F][p.S.F + 1] = p.S.S + 1;
          q.push({p.F, {p.S.F + 1, p.S.S + 1}});
        }
        if (p.F + 1 < n and p.S.S + 1 > u[p.F + 1][p.S.F] and
            c[p.F + 1][p.S.F] != '#') {
          u[p.F + 1][p.S.F] = p.S.S + 1;
          q.push({p.F + 1, {p.S.F, p.S.S + 1}});
        }
      }
    }
  }
  rep(i, n) {
    rep(j, m) {
      if (c[i][j] == '.' || c[i][j] == 'o') {
        v.pb(Edge(i * m + j + 1, flow, 1, 0));
      }
    }
  }
  cout << -min_cost_flow(flow + 2, v, inf, sink, flow);
}

int main() {
  // freopen("input.txt", "r", stdin);
  // freopen("output.txt", "w", stdout);
  // Red;
  int T = 1;
  // cin >> T;
  for (int i = 1; i <= T; ++i) {
    solve();
  }
  return 0;
}


#pragma GCC optimize(200000)
#include <cstdio>
#include <cstring>
#include <deque>
#include <iostream>
#define re register
using namespace std;
struct edge {
  int next, w, to, ww, fr;
} e[3000005];
const int Mxdt = 100000;
char gc() {
  static char buf[Mxdt], *p1 = buf, *p2 = buf;
  return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Mxdt, stdin), p1 == p2)
             ? EOF
             : *p1++;
}
#define gc getchar
int read() {
  int res = 0;
  char ch = gc();
  while (ch < '0' || ch > '9')
    ch = gc();
  while (ch >= '0' && ch <= '9')
    res = (res << 3) + (res << 1) + (ch ^ 48), ch = gc();
  return res;
}
int head[3002], b, c, d, n, m, cnt = 1, S, T, dis[3002], pre[3002], p[3002],
                               ans1, ans2, vv[3002];
void bu(register int u, register int v, register int w, re int ww) {
  if (w == 0)
    return;
  e[++cnt].to = v;
  e[cnt].ww = ww;
  e[cnt].w = w;
  e[cnt].fr = u;
  e[cnt].next = head[u];
  head[u] = cnt;
  e[++cnt].to = u;
  e[cnt].fr = v;
  e[cnt].ww = -ww;
  e[cnt].next = head[v];
  head[v] = cnt;
}
deque<int> q;
int spfa(register int s, register int t) {

  memset(p, 0, sizeof(p));
  memset(dis, 127, sizeof(dis));
  memset(vv, 0, sizeof(vv));
  bool zzw = 0;
  q.push_front(s);
  dis[s] = 0;
  vv[s] = 1;
  while (!q.empty()) {
    int r = q.front();
    q.pop_front();
    vv[r] = 0;
    if (r == t)
      zzw = 1;
    for (register int i = head[r]; i; i = e[i].next) {
      if (e[i].w && dis[e[i].to] > dis[r] + e[i].ww) {
        dis[e[i].to] = dis[r] + e[i].ww;
        p[e[i].to] = i;
        if (!vv[e[i].to]) {

          vv[e[i].to] = 1;
          if (!q.empty() && dis[e[i].to] < dis[q.front()])
            q.push_front(e[i].to);
          else
            q.push_back(e[i].to);
        }
      }
    }
  }
  if (zzw)
    return 1;
  return 0;
}
#define min mn
int min(re int x, re int y) { return x < y ? x : y; }
void dfs() {
  int tmp = 0x7fffffff, cnt = 0, pp;
  for (re int i = T; i != S; i = e[pp ^ 1].to) {
    pp = p[i];
    cnt += e[pp].ww;
    tmp = min(tmp, e[pp].w);
  }
  for (re int i = T; i != S; i = e[pp ^ 1].to) {
    pp = p[i];
    e[pp].w -= tmp, e[pp ^ 1].w += tmp;
  }

  ans1 += tmp, ans2 += tmp * cnt;
}
char s[502][502];
int vis[52][52];
inline void bfs(re int x, re int y, re int z) {
  if (x > n || y > m || s[x][y] == '#')
    return;
  vis[x][y] = z;
  bfs(x + 1, y, z + 1);
  bfs(x, y + 1, z + 1);
}
signed main() {
  //	freopen("a.out","w",stdout);
  n = read();
  m = read();
  S = n * m + 1, T = S + 1;
  for (re int i = 1; i <= n; ++i)
    scanf("%s", s[i] + 1);
  for (re int i = 1; i <= n; ++i) {
    for (re int j = 1; j <= m; ++j) {
      if (s[i][j] == '#')
        continue;
      if (s[i][j] == 'o') {
        bu(S, (i - 1) * m + j, 1, 0);
        memset(vis, 0, sizeof(vis));
        bfs(i, j, 0);
        for (re int ii = 1; ii <= n; ++ii) {
          for (re int jj = 1; jj <= m; ++jj) {
            if (vis[ii][jj])
              bu((i - 1) * m + j, (ii - 1) * m + jj, 1, -vis[ii][jj]);
          }
        }
      }
      bu((i - 1) * m + j, T, 1, 0);
    }
  }
  while (spfa(S, T))
    dfs();
  printf("%d", -ans2);
}

#pragma GCC optimize(200000)
#include <cstdio>
#include <cstring>
#include <deque>
#include <iostream>
#define re register
using namespace std;
struct edge {
  int next, w, to, ww, fr;
} e[3000005];
const int Mxdt = 100000;
char gc() {
  static char buf[Mxdt], *p1 = buf, *p2 = buf;
  return p1 == p2 && (p2 = (p1 = buf) + fread(buf, 1, Mxdt, stdin), p1 == p2)
             ? EOF
             : *p1++;
}
#define gc getchar
int read() {
  int res = 0;
  char ch = gc();
  while (ch < '0' || ch > '9')
    ch = gc();
  while (ch >= '0' && ch <= '9')
    res = (res << 3) + (res << 1) + (ch ^ 48), ch = gc();
  return res;
}
int head[3002], b, c, d, n, m, cnt = 1, S, T, dis[3002], pre[3002], p[3002],
                               ans1, ans2, vv[3002];
void bu(register int u, register int v, register int w, re int ww) {
  if (w == 0)
    return;
  e[++cnt].to = v;
  e[cnt].ww = ww;
  e[cnt].w = w;
  e[cnt].fr = u;
  e[cnt].next = head[u];
  head[u] = cnt;
  e[++cnt].to = u;
  e[cnt].fr = v;
  e[cnt].ww = -ww;
  e[cnt].next = head[v];
  head[v] = cnt;
}
deque<int> q;
int spfa(register int s, register int t) {

  memset(p, 0, sizeof(p));
  memset(dis, 127, sizeof(dis));
  memset(vv, 0, sizeof(vv));
  bool zzw = 0;
  q.push_front(s);
  dis[s] = 0;
  vv[s] = 1;
  while (!q.empty()) {
    int r = q.front();
    q.pop_front();
    vv[r] = 0;
    if (r == t)
      zzw = 1;
    for (register int i = head[r]; i; i = e[i].next) {
      if (e[i].w && dis[e[i].to] > dis[r] + e[i].ww) {
        dis[e[i].to] = dis[r] + e[i].ww;
        p[e[i].to] = i;
        if (!vv[e[i].to]) {

          vv[e[i].to] = 1;
          if (!q.empty() && dis[e[i].to] < dis[q.front()])
            q.push_front(e[i].to);
          else
            q.push_back(e[i].to);
        }
      }
    }
  }
  if (zzw)
    return 1;
  return 0;
}
#define min mn
int min(re int x, re int y) { return x < y ? x : y; }
void dfs() {
  int tmp = 0x7fffffff, cnt = 0, pp;
  for (re int i = T; i != S; i = e[pp ^ 1].to) {
    pp = p[i];
    cnt += e[pp].ww;
    tmp = min(tmp, e[pp].w);
  }
  for (re int i = T; i != S; i = e[pp ^ 1].to) {
    pp = p[i];
    e[pp].w -= tmp, e[pp ^ 1].w += tmp;
  }

  ans1 += tmp, ans2 += tmp * cnt;
}
char s[502][502];
int vis[52][52];
inline void bfs(re int x, re int y, re int z) {
  if (x > n || y > m || s[x][y] == '#')
    return;
  vis[x][y] = z;
  bfs(x + 1, y, z + 1);
  bfs(x, y + 1, z + 1);
}
signed main() {
  //	freopen("a.out","w",stdout);
  n = read();
  m = read();
  S = n * m + 1, T = S + 1;
  for (re int i = 1; i <= n; ++i)
    scanf("%s", s[i] + 1);
  for (re int i = 1; i <= n; ++i) {
    for (re int j = 1; j <= m; ++j) {
      if (s[i][j] == '#')
        continue;
      if (s[i][j] == 'o')
        bu(S, (i - 1) * m + j, 1, 0);
      if (s[i][j + 1] != '#' && j < m)
        bu((i - 1) * m + j, (i - 1) * m + j + 1, 1 << 29, -1);
      if (s[i + 1][j] != '#' && i < n)
        bu((i - 1) * m + j, i * m + j, 1 << 29, -1);
      bu((i - 1) * m + j, T, 1, 0);
    }
  }
  while (spfa(S, T))
    dfs();
  printf("%d", -ans2);
}


#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>/
// #include <ext/pb_ds/tree_policy.hpp>

#define pb push_back
#define mp make_pair
#define PI (acos(-1.0))
#define IN freopen("nocross.in", "r", stdin)
#define OUT freopen("nocross.out", "w", stdout)
#define FOR(i, a, b) for (i = a; i <= b; i++)
#define DBG printf("Hi\\n")
#define i64 long long int
#define eps (1e-8)
#define xx first
#define yy second
#define ln 17
#define off 2
#define SZ(z) ((int)z.size())

#define FastIO                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL)

// using namespace __gnu_pbds;
using namespace std;

// typedef tree< i64, null_type, less<i64>, rb_tree_tag,
// tree_order_statistics_node_update> ordered_set;

// #define maxn 1000005
// #define INF 2000000000000000000LL
// #define mod 998244353LL

namespace mcmf {
// this should be at least 2*( added edge in the graph )
const int MAX = 31000;
const long long INF = 1LL << 60;

long long cap[MAX], flow[MAX], cost[MAX], dis[MAX];
int n, m, s, t, Q[MAX * 10], adj[MAX], link[MAX], last[MAX], from[MAX],
    visited[MAX];

void init(int nodes, int source, int sink) {
  m = 0, n = nodes, s = source, t = sink;
  for (int i = 0; i <= n; i++)
    last[i] = -1;
}

void addEdge(int u, int v, long long c, long long w) {
  adj[m] = v, cap[m] = c, flow[m] = 0, cost[m] = +w, link[m] = last[u],
  last[u] = m++;
  adj[m] = u, cap[m] = 0, flow[m] = 0, cost[m] = -w, link[m] = last[v],
  last[v] = m++;
}

bool spfa() {
  int i, j, x, f = 0, l = 0;
  for (i = 0; i <= n; i++)
    visited[i] = 0, dis[i] = INF;

  dis[s] = 0, Q[l++] = s;
  while (f < l) {
    i = Q[f++];
    for (j = last[i]; j != -1; j = link[j]) {
      if (flow[j] < cap[j]) {
        x = adj[j];
        if (dis[x] > dis[i] + cost[j]) {
          dis[x] = dis[i] + cost[j], from[x] = j;
          if (!visited[x]) {
            visited[x] = 1;
            if (f && rand() & 7)
              Q[--f] = x;
            else
              Q[l++] = x;
          }
        }
      }
    }
    visited[i] = 0;
  }
  return (dis[t] != INF);
}

pair<long long, long long> solve() {
  int i, j;
  long long mincost = 0, maxflow = 0;

  while (spfa()) {
    long long aug = INF;
    for (i = t, j = from[i]; i != s; i = adj[j ^ 1], j = from[i]) {
      aug = min(aug, cap[j] - flow[j]);
    }
    for (i = t, j = from[i]; i != s; i = adj[j ^ 1], j = from[i]) {
      flow[j] += aug, flow[j ^ 1] -= aug;
    }
    maxflow += aug, mincost += aug * dis[t];
  }
  return make_pair(mincost, maxflow);
}
} // namespace mcmf

int dp[55][55][55][55];
char s[55][55];
int cnt[55][55];

int main() {
  int n, m;
  scanf("%d %d", &n, &m);

  for (int i = 0; i < n; i++) {
    scanf("%s", s[i]);
  }

  for (int i = n - 1; i >= 0; i--) {
    for (int j = m - 1; j >= 0; j--) {
      if (s[i][j] == '#')
        continue;
      dp[i][j][i][j] = 1;

      if (i < n - 1) {
        for (int k = 0; k < n; k++) {
          for (int l = 0; l < m; l++)
            dp[i][j][k][l] |= dp[i + 1][j][k][l];
        }
      }
      if (j < m - 1) {
        for (int k = 0; k < n; k++) {
          for (int l = 0; l < m; l++)
            dp[i][j][k][l] |= dp[i][j + 1][k][l];
        }
      }
    }
  }

  int cntob = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++)
      if (s[i][j] == 'o')
        cnt[i][j] = cntob++;
  }

  int src = cntob + n * m, sink = cntob + n * m + 1;

  mcmf::init(cntob + n * m + 2, src, sink);

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (s[i][j] != 'o')
        continue;

      for (int k = 0; k < n; k++) {
        for (int l = 0; l < m; l++) {
          if (dp[i][j][k][l])
            mcmf::addEdge(cnt[i][j], cntob + k * m + l, 1,
                          -((k + l) - (i + j)));
        }
      }
    }
  }

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (s[i][j] == 'o')
        mcmf::addEdge(src, cnt[i][j], 1, 0);
      if (s[i][j] != '#')
        mcmf::addEdge(cntob + i * m + j, sink, 1, 0);
    }
  }

  pair<int, int> ans = mcmf::solve();

  printf("%d\\n", -ans.xx);

  return 0;
}

#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>/
// #include <ext/pb_ds/tree_policy.hpp>

#define pb push_back
#define mp make_pair
#define PI (acos(-1.0))
#define IN freopen("nocross.in", "r", stdin)
#define OUT freopen("nocross.out", "w", stdout)
#define FOR(i, a, b) for (i = a; i <= b; i++)
#define DBG printf("Hi\\n")
#define i64 long long int
#define eps (1e-8)
#define xx first
#define yy second
#define ln 17
#define off 2
#define SZ(z) ((int)z.size())

#define FastIO                                                                 \\
  ios_base::sync_with_stdio(false);                                            \\
  cin.tie(NULL)

// using namespace __gnu_pbds;
using namespace std;

// typedef tree< i64, null_type, less<i64>, rb_tree_tag,
// tree_order_statistics_node_update> ordered_set;

// #define maxn 1000005
// #define INF 2000000000000000000LL
// #define mod 998244353LL

namespace mcmf {
// this should be at least 2*( added edge in the graph )
const int MAX = 500000;
const long long INF = 1LL << 60;

long long cap[MAX], flow[MAX], cost[MAX], dis[MAX];
int n, m, s, t, Q[MAX * 10], adj[MAX], link[MAX], last[MAX], from[MAX],
    visited[MAX];

void init(int nodes, int source, int sink) {
  m = 0, n = nodes, s = source, t = sink;
  for (int i = 0; i <= n; i++)
    last[i] = -1;
}

void addEdge(int u, int v, long long c, long long w) {
  adj[m] = v, cap[m] = c, flow[m] = 0, cost[m] = +w, link[m] = last[u],
  last[u] = m++;
  adj[m] = u, cap[m] = 0, flow[m] = 0, cost[m] = -w, link[m] = last[v],
  last[v] = m++;
}

bool spfa() {
  int i, j, x, f = 0, l = 0;
  for (i = 0; i <= n; i++)
    visited[i] = 0, dis[i] = INF;

  dis[s] = 0, Q[l++] = s;
  while (f < l) {
    i = Q[f++];
    for (j = last[i]; j != -1; j = link[j]) {
      if (flow[j] < cap[j]) {
        x = adj[j];
        if (dis[x] > dis[i] + cost[j]) {
          dis[x] = dis[i] + cost[j], from[x] = j;
          if (!visited[x]) {
            visited[x] = 1;
            if (f && rand() & 7)
              Q[--f] = x;
            else
              Q[l++] = x;
          }
        }
      }
    }
    visited[i] = 0;
  }
  return (dis[t] != INF);
}

pair<long long, long long> solve() {
  int i, j;
  long long mincost = 0, maxflow = 0;

  while (spfa()) {
    long long aug = INF;
    for (i = t, j = from[i]; i != s; i = adj[j ^ 1], j = from[i]) {
      aug = min(aug, cap[j] - flow[j]);
    }
    for (i = t, j = from[i]; i != s; i = adj[j ^ 1], j = from[i]) {
      flow[j] += aug, flow[j ^ 1] -= aug;
    }
    maxflow += aug, mincost += aug * dis[t];
  }
  return make_pair(mincost, maxflow);
}
} // namespace mcmf

int dp[55][55][55][55];
char s[55][55];
int cnt[55][55];

int main() {
  int n, m;
  scanf("%d %d", &n, &m);

  for (int i = 0; i < n; i++) {
    scanf("%s", s[i]);
  }

  for (int i = n - 1; i >= 0; i--) {
    for (int j = m - 1; j >= 0; j--) {
      if (s[i][j] == '#')
        continue;
      dp[i][j][i][j] = 1;

      if (i < n - 1) {
        for (int k = 0; k < n; k++) {
          for (int l = 0; l < m; l++)
            dp[i][j][k][l] |= dp[i + 1][j][k][l];
        }
      }
      if (j < m - 1) {
        for (int k = 0; k < n; k++) {
          for (int l = 0; l < m; l++)
            dp[i][j][k][l] |= dp[i][j + 1][k][l];
        }
      }
    }
  }

  int cntob = 0;
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++)
      if (s[i][j] == 'o')
        cnt[i][j] = cntob++;
  }

  int src = cntob + n * m, sink = cntob + n * m + 1;

  mcmf::init(cntob + n * m + 2, src, sink);

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (s[i][j] != 'o')
        continue;

      for (int k = 0; k < n; k++) {
        for (int l = 0; l < m; l++) {
          if (dp[i][j][k][l])
            mcmf::addEdge(cnt[i][j], cntob + k * m + l, 1,
                          -((k + l) - (i + j)));
        }
      }
    }
  }

  for (int i = 0; i < n; i++) {
    for (int j = 0; j < m; j++) {
      if (s[i][j] == 'o')
        mcmf::addEdge(src, cnt[i][j], 1, 0);
      if (s[i][j] != '#')
        mcmf::addEdge(cntob + i * m + j, sink, 1, 0);
    }
  }

  pair<int, int> ans = mcmf::solve();

  printf("%d\\n", -ans.xx);

  return 0;
}