#include <iostream>
#include <map>
#include <set>
#include <algorithm>
#include <vector>
#include <sstream>
#include <string>
#include <functional>
#include <queue>
#include <deque>
#include <stack>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#include <fstream>
#include <iterator>
#include <random>
#include <chrono>


#define forr(i,start,count) for (int i = (start); i < (start)+(count); ++i)
#define set_map_includes(set, elt) (set.find((elt)) != set.end())
#define readint(i) int i; cin >> i
#define readll(i) ll i; cin >> i
#define readdouble(i) double i; cin >> i
#define readstring(s) string s; cin >> s

typedef long long ll;

using namespace std;

ll modd = 1000 * 1000 * 1000 + 7;

template<class T>
class MinCostMaxFlow {
    // between two vertices there can only be one directed edge!!! if you want the trick with several edges between vertices,  break down
    // the edge into two pieces by introduction of new vertex
    // costs can be negative!! (capacities ofcourse not)
    public:
     struct Edge  {   int from, to;
                      T capacity, cost; };
     vector<vector<int>> adj;
     vector<vector<T>> cost, capacity;
     vector<Edge> edges;
     const ll INF = modd*modd;

     void add_edge(int fr, int tt, T cap, T cst) {
         edges.push_back({fr, tt, cap, cst});
     }

     void clear_edges() {
         edges.clear();
     }

void shortest_paths(int n, int v0, vector<T>& d, vector<T>& 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);
                }
            }
        }
    }
}

T min_cost_flow(int N, T K, int s, int t) {
    // looks for min cost flow among all flows of total flow less or equal K;
    // return min cost flow among flows of total flow K, or if such flow doesnt exist, min cost flow for max flow
    // if you need the value of output flow, it's stored in variable flow at the time of return
    // if you need min cost flow among all flows <= K, take minimum of flow where it says here!!!!! and store it and output it, bc cost can
    // decrease in next iteration when flow increases
    // if you need min cost max flow, take very large K
    adj.assign(N, vector<int>());
    cost.assign(N, vector<T>(N, 0));
    capacity.assign(N, vector<T>(N, 0));
    map<pair<int,int>, T> ret;
    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;
    }

    T flow = 0;
    T cost = 0;
    vector<T> d, p;
    while (flow < K) {
        shortest_paths(N, s, d, p);
        if (d[t] == INF)
            break;

        T 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;
        // here!!!!
        while (cur != s) {
            ret[make_pair(p[cur],cur)] += f;
            capacity[p[cur]][cur] -= f;
            capacity[cur][p[cur]] += f;
            cur = p[cur];
        }
    }

    return cost;
}
};


int main()   {

      ios_base::sync_with_stdio(false);

      cout.precision(17);
      mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());
      uniform_int_distribution<int> rand_gen(0, modd);   // rand_gen(rng) gets the rand no

//    readint(test_cases);
    int test_cases = 1;
    forr(t, 1, test_cases) {
        readint(n); readint(m);
        vector<string> grid;
        forr(i,0,n) {
            readstring(s); grid.push_back(s);
        }

        map<pair<int,int>, vector<pair<int,int>>> lengths;
        forr(i,0,n) {
            forr(j,0,m) {
                if (grid[i][j]!='o') {continue;}
                stack<pair<int,int>> todo;
                todo.push(make_pair(i,j));
                vector<vector<bool>> visited(n, vector<bool>(m, false));
                while (!todo.empty()) {
                    auto curr = todo.top(); todo.pop();
                    if (!visited[curr.first][curr.second]) {
                      visited[curr.first][curr.second] = true;
                      lengths[make_pair(i,j)].push_back(curr);
                      if ((curr.first+1<n) && (grid[curr.first+1][curr.second]!='#')) {          todo.push(make_pair(curr.first+1,curr.second));        }
                      if ((curr.second+1<m) && (grid[curr.first][curr.second+1]!='#')) {          todo.push(make_pair(curr.first,curr.second+1));                }
                    }
                }
            }
        }

        
        MinCostMaxFlow<ll> mc;
        int i = 0;
        for(auto x : lengths) {
            for(auto goal : x.second) {
              mc.add_edge(i, goal.first*m+goal.second + lengths.size(), 1, -(abs(goal.first-x.first.first)+abs(goal.second-x.first.second)));
            }
            mc.add_edge(n*m+lengths.size(), i, 1, 0);
            ++i;
        }
        forr(i,0,n) {
            forr(j,0,m) {
                mc.add_edge(lengths.size() + i*m+j, n*m+lengths.size()+1, 1, 0);
            }
        }

        auto q = mc.min_cost_flow(lengths.size() + n*m + 2, modd, lengths.size()+m*n, lengths.size()+m*n+1);

        cout << -q << endl;

    }
    return 0;
}


Measured memory_bytes: 112444