#include <iostream>
#include <string>
#include <vector>
#include <array>
#include <queue>
#include <deque>
#include <algorithm>
#include <set>
#include <map>
#include <bitset>
#include <cmath>
#include <functional>
#include <cassert>
#include <iomanip>
#define vll vector<ll>
#define vvvl vector<vvl>
#define vvl vector<vector<ll>>
#define VV(a, b, c, d) vector<vector<d>>(a, vector<d>(b, c))
#define VVV(a, b, c, d) vector<vvl>(a, vvl(b, vll (c, d)));
#define re(c, b) for(ll c=0;c<b;c++)
#define all(obj) (obj).begin(), (obj).end()
typedef long long int ll;
typedef long double ld;
using namespace std;


/*
始点、終点、フロー固定、負の辺を扱える

flow():
  pair(達成できたフロー、最小コスト)を返す
  指定した流量と異なる場合例外処理する必要がある

正の辺の場合edge.flow流れている
負の辺の場合edge.cap - edge.flow流れている
*/
template <class T>
struct simple_queue{
  vector<T> payload;
  int pos = 0;
  void reserve(int n) { payload.reserve(n); }
  int size() const { return int(payload.size()) - pos; }
  bool empty() const { return pos == int(payload.size()); }
  void push(const T& t) { payload.push_back(t); }
  T& front() { return payload[pos]; }
  void clear() {
    payload.clear();
    pos = 0;
  }
  void pop() { pos++; }
};

struct mcf_graph {
public:
  using Cap = ll;
  using Cost = ll;
  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,};
  }
  vector<edge> edges() {
    int m = int(pos.size());
    vector<edge> result(m);
    for(int i=0;i<m;i++) result[i] = get_edge(i);
    return result;
  }

  pair<Cap, Cost> flow(int s, int t) {
    return flow(s, t, numeric_limits<Cap>::max());
  }
  pair<Cap, Cost> flow(int s, int t, Cap flow_limit) {
    return slope(s, t, flow_limit).back();
  }
  vector<pair<Cap, Cost>> slope(int s, int t) {
    return slope(s, t, numeric_limits<Cap>::max());
  }

  vector<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
    vector<Cost> dual(_n, 0), dist(_n);
    vector<int> pv(_n), pe(_n);
    vector<bool> vis(_n);
    auto dual_ref = [&]() {
      fill(dist.begin(), dist.end(), numeric_limits<Cost>::max());
      fill(pv.begin(), pv.end(), -1);
      fill(pe.begin(), pe.end(), -1);
      fill(vis.begin(), vis.end(), false);
      struct Q {
        Cost key;
        int to;
        bool operator<(Q r) const { return key > r.key; }
      };
      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;
    vector<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 = 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;
    };
    vector<pair<int, int>> pos;
    vector<vector<_edge>> g;
};

struct Dijkstra{
  ll INF=1000000000000000009;
  struct edge{ll to, cost;};
  typedef pair<ll, ll> p;
  vector<vector<edge>> G;
  vector<ll> pre, d;
  priority_queue<p, vector<p>, greater<p> > que; //class, inner, compair

  Dijkstra(ll s){
    s++;
    G.resize(s);
    pre.resize(s, -1);
    d.resize(s, INF);
  }
  inline void insert(ll a, ll b, ll c){ G[a].push_back(edge{b, c});}
  void init(){ for(int i=0;i<d.size();i++) pre[i] = -1, d[i] = INF;}
  void dijkstra(int start){
    init();
    d[start]=0;
    que.push(p(0, start));
    while(!que.empty()){
      p p_now=que.top(); que.pop();
      ll v = p_now.second;
      if(d[v]<p_now.first) continue;
      for(int i=0;i<G[v].size();i++){
        edge e = G[v][i];
        if(d[e.to]>d[v]+e.cost){
          d[e.to]=d[v]+e.cost;
          pre[e.to] = v;
          que.push(p(d[e.to], e.to));
        }
      }
    }
  }
  vll get_path(int now){
    vll ret;
    while(now!=-1){
      ret.push_back(now);
      now = pre[now];
    }
    return ret;
  }
  ll& operator [](int idx){return d[idx];}
};

int main(){
  ll n, m;std::cin >> n >> m;

  vector<string> s(n);
  ll cnt = 0;
  re(i, n) {
    std::cin >> s[i];
    for(int j=0;j<m;j++) if(s[i][j]=='o') cnt++;
  }

  int S = 2500, T = 2501;
  mcf_graph g(2605);
  vvl d = VV(0, 2, 0, ll);
  int dx[2] = {0, 1};
  int dy[2] = {1, 0};
  Dijkstra dj(n*m+1);

  for(int i=0;i<n;i++){
    for(int j=0;j<m;j++){
      if(s[i][j]=='#') continue;
      if(s[i][j]=='o') d.push_back({i, j});
      for(int k=0;k<2;k++){
        ll x = i + dx[k];
        ll y = j + dy[k];
        if(x==n||y==m) continue;
        if(s[x][y]=='#') continue;
        dj.insert(i*m+j, x*m+y, 1);
      }
    }
  }

  for(int i=0;i<d.size();i++) g.add_edge(S, 2502+i, 1LL, 0LL);
  for(int i=0;i<n;i++){
    for(int j=0;j<m;j++){
      if(s[i][j]=='#') continue;
      g.add_edge(i*m+j, T, 1LL, 0LL);
    }
  }

  //各コマについて、各空きマスに辺を伸ばす
  for(int i=0;i<d.size();i++){
    ll x = d[i][0], y = d[i][1];
    dj.dijkstra(x*m+y);
    for(int j=0;j<n;j++){
      for(int k=0;k<m;k++){
        if(s[j][k]=='#') continue;
        ll dist = dj[j*m+k];
        if(dist==dj.INF) continue;
        g.add_edge(2502+i, j*m+k, 1, 1000000-dist);//dist回移動できる
      }
    }
  }
  ll L = g.flow(S, T, cnt).second - 1000000LL * cnt;
  std::cout << -L << '\n';
}


Measured memory_bytes: 9024