#include <bits/stdc++.h>
using namespace std;

const int INF = numeric_limits<int>::max()/2;
const int NODES = 50*50+100;

struct edge {
	int u, v, cap, cst, rev;
};
vector<edge> E;
vector<int> al[NODES];

void add_edge(int u, int v, int cap, int cst) {
	int eid = E.size();
	al[u].push_back(eid);
	E.push_back(edge({u, v, cap, cst, eid+1}));
	al[v].push_back(eid+1);
	E.push_back(edge({v, u, 0, -cst, eid}));
}


int inQ[NODES], parent[NODES], D[NODES];

struct MCMF { // O(max_flow * augment)
	void augment_spfa(int s) {  // Fills parent and D.
		fill(parent, parent+NODES, -1);
		fill(D, D+NODES, INF);
		fill(inQ, inQ+NODES, 0);
		D[s] = 0; inQ[s] = 1;
		queue<int> q; q.push(s);
		while (!q.empty()) { // Loops forever on negative cycle.
			int at = q.front(); q.pop();
			inQ[at] = 0;
			for (int e : al[at]) if (E[e].cap) {
				int c = E[e].v, w = E[e].cst;
				if (D[at] + w < D[c]) {
					D[c] = D[at] + w;
					parent[c] = e;
					if (!inQ[c]) { q.push(c); inQ[c] = 1; }
				}
			}
		}
	}
	// Dijkstras can be worth it if SPFA is getting rekt by non-random input
	vector<int> pi;
	void augment_dijks(int s) {
		if (pi.size() == 0) { // Get rid of negative edge costs
			pi.assign(NODES+1, 0); // For dijkstra potentials. N+1 for special node
			// vector<int> tmp(N); // Can remove the rest of this for no-neg weights input
			// for (int i = 0; i < N; ++i) tmp[i] = i;
			// als.push_back(tmp); costs.push_back(vector<int>(N)); // Special node
			// for (int i = 0; i < N; ++i) // Bellman-ford V-1 iterations
			// 	for (int u = 0; u <= N; ++u) for (int c : als[u]) if (u >= N || caps[u][c]) {
			// 		pi[c] = min(pi[c], costs[u][c] + pi[u]); 
			// 	}
			// als.pop_back(); costs.pop_back();
		}
		fill(parent, parent+NODES, -1);
		fill(D, D+NODES, INF);
		map<int, queue<pair<int, int>>> pq;
	
		pq[0].emplace(s, -1);
		while (pq.size()) {
			// assert(pq.size() < 5);
			int at, paredge, d;
			auto& kv = *pq.begin();
			d = kv.first;
			tie(at, paredge) = kv.second.front();
			kv.second.pop();
			if (kv.second.empty()) {
				pq.erase(kv.first);
			}
			if (d > D[at]) continue;
			parent[at] = paredge;
			for (int e : al[at]) if (E[e].cap) {
				int c = E[e].v, w = E[e].cst;
				w += pi[at] - pi[c];
				if (w+d < D[c]) {
					D[c] = w+d;
					pq[w+d].emplace(c, e);
				}
			}
		}
		for (int i = 0; i < NODES; ++i) if (parent[i] != -1) 
			pi[i] += D[i]; // Maintain no neg cost edges
	}
	pair<int, int> run(int s, int t) {
		int flow = 0; int fcost = 0;
		augment_spfa(s);
		while (parent[t] != -1) { // Keep finding augmenting paths.
			int bottle = INF;
			for (int at = t; at != s; at = E[parent[at]].u) { // Find bottleneck.
				bottle = min(bottle, E[parent[at]].cap);
			}
			flow += bottle;
			for (int at = t; at != s; at = E[parent[at]].u) { // Push the flow.
				edge& e = E[parent[at]];
				edge& re = E[e.rev];
				fcost += bottle*e.cst;
				e.cap -= bottle;
				re.cap += bottle;
			}
			augment_spfa(s);
		}
		return {fcost, flow};
	}
};

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0);

	int N, M;
	cin >> N >> M;
	int locs = N*M;
	vector<string> G(N);
	for (auto& s : G) cin >> s;
	for (int i = 0; i < N; ++i) {
		for (int j = 0; j < M; ++j) {
			if (G[i][j] == '#') continue;
			if (i+1 < N && G[i+1][j] != '#') {
				add_edge(i*M+j,(i+1)*M+j,1e9,-1);
			}
			if (j+1 < M && G[i][j+1] != '#') {
				add_edge(i*M+j,i*M+(j+1),1e9,-1);
			}
			if (G[i][j] == 'o') {
				add_edge(locs, i*M+j, 1, 0);
			}
			add_edge(i*M+j, locs+1, 1, 0);
		}
	}
	MCMF mcmf;
	auto res = mcmf.run(locs, locs+1);
	// cout << res.first << " " << res.second << endl;
	cout << -res.first << endl;

	// for (auto e : E) {
	// 	if (e.cap > 10 && e.cap < (int)1e9)
	// 		cout << e.u/M << " " << e.u%M << " " << e.v/M << " " << e.v%M << " " << e.cap << " " << e.cst << " " << e.rev << endl;
	// }
}


Measured memory_bytes: 3912