#pragma GCC optimize("O3")
#include <atcoder/all>
using namespace atcoder;
#include <bits/stdc++.h>
// #include <ext/pb_ds/assoc_container.hpp>
// using namespace __gnu_pbds;
// gp_hash_table<int, int> mapka;

using namespace std;
#define PB push_back
#define MP make_pair
#define LL long long
#define int LL
#define FOR(i,a,b) for(int i = (a); i <= (b); i++)
#define RE(i,n) FOR(i,1,n)
#define REP(i,n) FOR(i,0,(int)(n)-1)
#define R(i,n) REP(i,n)
#define VI vector<int>
#define PII pair<int,int>
#define LD long double
#define FI first
#define SE second
#define st FI
#define nd SE
#define ALL(x) (x).begin(), (x).end()
#define SZ(x) ((int)(x).size())

template<class C> void mini(C &a4, C b4) { a4 = min(a4, b4); }
template<class C> void maxi(C &a4, C b4) { a4 = max(a4, b4); }

template<class TH> void _dbg(const char *sdbg, TH h){ cerr<<sdbg<<'='<<h<<endl; }
template<class TH, class... TA> void _dbg(const char *sdbg, TH h, TA... a) {
  while(*sdbg!=',')cerr<<*sdbg++;
  cerr<<'='<<h<<','; _dbg(sdbg+1, a...);
}

template<class T> ostream &operator<<(ostream& os, vector<T> V) {
  os << "["; for (auto vv : V) os << vv << ","; return os << "]";
}
template<class L, class R> ostream &operator<<(ostream &os, pair<L,R> P) {
  return os << "(" << P.st << "," << P.nd << ")";
}

#ifdef LOCAL
#define debug(...) _dbg(#__VA_ARGS__, __VA_ARGS__)
#else
#define debug(...) (__VA_ARGS__)
#define cerr if(0)cout
#endif

class MinCostFlow {
  
  struct MCEdge {
    int to, cost, flow;
    MCEdge* next;
    MCEdge(){}
    MCEdge(int t, int c, int f) : to(t), cost(c), flow(f) {}
  };

  const int Infty = 1010101010;
  
  vector<vector<MCEdge*>> adjList;
  int N, Source, Sink;
  vector<int> dist;
  vector<MCEdge*> prev, Edges;

  void spfa() {
    queue<int> Q; vector<bool> onQueue(N); fill(ALL(dist), Infty);
    Q.push(Source); onQueue[Source] = true; dist[Source] = 0;
    while (!Q.empty()) {
      int v = Q.front(); Q.pop(); onQueue[v] = false;
      for (MCEdge *E : adjList[v]) {
        int s = E->to;
        if (E->flow == 0) { continue; }

        int newDist = dist[v] + E->cost;
        if (newDist < dist[s]) {
          dist[s] = newDist;
          prev[s] = E->next;
          if (!onQueue[s]) {
            Q.push(s); onQueue[s] = true;
          }
        }
      }
    }
  }
  
  int reduce_cost(){
    int result = dist[Sink];
    for(int v = 0; v < N; v++){
      for(MCEdge* E: adjList[v]){
        E->cost += dist[v] - dist[E->to];
      }
    }
    return result;
  }
  
  void dijkstra_shortest_path(){
    fill(dist.begin(), dist.end(), Infty);
    dist[Source] = 0;
    
    priority_queue<pair<int,int>> Q;
    Q.push(make_pair(0, Source));
    
    while(!Q.empty()){
      int dst, v;
      tie(dst, v) = Q.top(); Q.pop();
      dst = -dst;
      if(dst != dist[v]) continue;
      
      for(MCEdge* E: adjList[v]){
        int s = E->to;
        if(E->flow == 0) continue;
        
        int newDist = dist[v] + E->cost;
        if(newDist < dist[s]){
          dist[s] = newDist;
          prev[s] = E->next;
          Q.push(make_pair(-newDist, s));
        }
      }
    }
  }
  
public:
  MinCostFlow(){N = 0;}
  ~MinCostFlow(){
      for(MCEdge* E: Edges)
        delete E;
  }
  int numEdges = 0;

  void fit(int size) {
    if (size > N) {
      N = size;
      dist.resize(size); prev.resize(size); adjList.resize(size);
    }
  }
  
  void add_edge(int u, int v, int flow, int cost){
    fit(max(u, v) + 1);
    numEdges++;
    MCEdge * E1, * E2;
    E1 = new MCEdge(v, cost, flow);
    E2 = new MCEdge(u, -cost, 0);
    Edges.push_back(E1);
    Edges.push_back(E2);
    E1->next = E2;
    E2->next = E1;
    debug(SZ(adjList),u,v,N);
    adjList[u].push_back(E1);
    adjList[v].push_back(E2);
  }
  
  pair<int,int> get_min_cost_flow(int s, int t){
    fit(max(s, t) + 1);
    //cout << numEdges << endl;
    Source = s; Sink = t;
    int cost = 0, flow = 0;
    
    spfa();
    
    if(dist[Sink] > Infty/2) return {0,0};
    int sinkCost = reduce_cost();
    
    while(true){
      dijkstra_shortest_path();
      if(dist[Sink] > Infty/2) break;
      sinkCost += reduce_cost();
      
      int maxSend = Infty;
      for(int v = Sink; v != Source; v = prev[v]->to){
        maxSend = min(maxSend, prev[v]->next->flow);
      }
      
      for(int v = Sink; v != Source; v = prev[v]->to){
        MCEdge* E1 = prev[v];
        MCEdge* E2 = E1->next;
        E1->flow += maxSend;
        E2->flow -= maxSend;
      }
      
      flow += maxSend;
      cost += maxSend * sinkCost;
    }
    
    return {flow, cost};
  }
};

int n,m;
int get(int a,int b){
  return a * m + b;
}
const int inf = 1e6;
int32_t main() {
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout << fixed << setprecision(11);
  cerr << fixed << setprecision(6);
  cin >> n >> m;
  MinCostFlow flow;
  vector<string> b(n);
  R(i,n){
    cin >> b[i];
  }
  int s = n * m;
  int t = n * m + 1;
  R(i,n)R(j,m){
    if(b[i][j] == 'o'){
      flow.add_edge(s,get(i,j), 1, 0);
    }
    if(b[i][j] != '#'){
      flow.add_edge(get(i,j),t, 1, 0);
    }
  }
  R(i,n)R(j,m-1){
    if(b[i][j] != '#' && b[i][j+1] != '#'){
      flow.add_edge(get(i,j), get(i,j+1), inf, -1);
    }
  }
  R(i,n-1)R(j,m){
    if(b[i][j] != '#' && b[i+1][j] != '#'){
      flow.add_edge(get(i,j), get(i+1,j), inf, -1);
    }
  }
  cout << -flow.get_min_cost_flow(s,t).SE << "\n";
}


Measured memory_bytes: 4672