/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author aajisaka
 */


#include<bits/stdc++.h>

using namespace std;

void debug_out() { cerr << endl; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
  cerr << " " << to_string(H);
  debug_out(T...);
}
#ifdef LOCAL
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif

#define SPEED ios_base::sync_with_stdio(false);cin.tie(nullptr)
#define rep(i,n) for(int i=0; i<(int)(n); i++)
#define all(v) v.begin(), v.end()
template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return true; } return false; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return true; } return false; }

using ll = long long;
using ull = unsigned long long;
using P = pair<ll, ll>;

constexpr long double PI = 3.14159265358979323846264338327950288L;
mt19937_64 rng(chrono::steady_clock::now().time_since_epoch().count());


#include <algorithm>
#include <cassert>
#include <limits>
#include <queue>
#include <vector>

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())});
      int from_id = int(g[from].size());
      int to_id = int(g[to].size());
      if (from == to) to_id++;
      g[from].push_back(_edge{to, to_id, cap, cost});
      g[to].push_back(_edge{from, from_id, 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_per_flow = -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_per_flow == d) {
          result.pop_back();
        }
        result.push_back({flow, cost});
        prev_cost_per_flow = d;
      }
      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;
};


class CMovingPieces {
public:
    void solve(istream& cin, ostream& cout) {
      SPEED;
      int n, m; cin >> n >> m;
      vector<string> mat(n);
      rep(i, n) {
        cin >> mat[i];
      }

      vector<P> start;
      rep(i, n) {
        rep(j, m) {
          if (mat[i][j] == 'o') {
            start.emplace_back(i, j);
            debug(i, j);
          }
        }
      }

      auto mcf = mcf_graph<int, ll>(2*n*m+2);

      vector<vector<int>> dp(n, vector<int>(m));
      int inf = 1e+7;

      vector<int> edges;

      for(auto e: start) {
        rep(i, n) rep(j, m) {
          dp[i][j] = INT_MAX/2;
        }
        auto x = e.first;
        auto y = e.second;
        mcf.add_edge(2*n*m, x*m+y, 1, 0);
        dp[x][y] = 0;
        queue<P> que;
        que.emplace(x, y);
        while(!que.empty()) {
          auto p = que.front(); que.pop();
          x = p.first;
          y = p.second;
          if (x+1 < n && mat[x+1][y] != '#') {
            if (dp[x+1][y] > dp[x][y] + 1) {
              dp[x+1][y] = dp[x][y] + 1;
              que.emplace(x+1, y);
            }
          }
          if (y+1 < m && mat[x][y+1] != '#') {
            if (dp[x][y+1] > dp[x][y] + 1) {
              dp[x][y+1] = dp[x][y] + 1;
              que.emplace(x, y+1);
            }
          }
        }
        rep(i, n) rep(j, m) {
          if (dp[i][j] != INT_MAX/2) {
            int idx = mcf.add_edge(e.first*m+e.second, n*m+i*m+j, 1, inf-dp[i][j]);
            edges.push_back(idx);
          }
        }
      }

      rep(i, n) rep(j, m) {
        if (dp[i][j] != '#') {
          mcf.add_edge(n*m+i*m+j, 2*n*m+1, 1, 0);
        }
      }

      mcf.add_edge(2*n*m, 2*n*m+1, n*m, inf*100);
      auto ret = mcf.flow(2*n*m, 2*n*m+1, start.size());



      ll ans = inf*start.size() - ret.second;
      cout << ans << endl;

      for(auto e: edges) {
        auto edge = mcf.get_edge(e);
        if (edge.flow == 1) debug(edge.from, edge.to, edge.flow, edge.cost);
      }

    }
};

signed main() {
  CMovingPieces solver;
  std::istream& in(std::cin);
  std::ostream& out(std::cout);
  solver.solve(in, out);
  return 0;
}

Measured memory_bytes: 9156