#include <iostream>
#include <algorithm>
#include <iomanip>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <vector>
#include <string>
#include <queue>
#include <deque>
#include <list>
#include <set>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <bitset>
#include <random>
#include <complex>
#include <assert.h>

#include <atcoder/dsu.hpp>
#include <atcoder/fenwicktree.hpp>
#include <atcoder/segtree.hpp>
#include <atcoder/lazysegtree.hpp>
#include <atcoder/maxflow.hpp>
#include <atcoder/mincostflow.hpp>

using namespace std;
using namespace atcoder;

////////////////	型名など
typedef long long ll;
#define endl '\n'

////////////////	各種定数
const ll INF = 1LL << 60;
const double EPS = 1e-12;

////////////////	便利なテンプレート関数
template<typename T>
inline bool chmin(T& x, T a) {
	if (x >= a) { x = a; return true; }
	return false;
}

template<typename T>
inline bool chmax(T& x, T a) {
	if (x <= a) { x = a; return true; }
	return false;
}

////////////////	平面から直線への写像
template<typename T> struct HashMap2D {
	HashMap2D() {
		Hmax = 1000000007, Hmin = -1000000007;
		Wmax = 1000000007, Wmin = -1000000007;
		Height = Hmax - Hmin;
		Width = Wmax - Wmin;
	}
	HashMap2D(T H_max, T W_max, T H_min = 0, T W_min = 0) {
		Hmax = H_max; Hmin = H_min;
		Wmax = W_max; Wmin = W_min;
		Height = Hmax - Hmin;
		Width = Wmax - Wmin;
	}
	T hash(T h, T w) {
		h -= Hmin, w -= Wmin;
		return Width * h + w;
	}
	pair<T, T> inv(T index) {
		pair<T, T> p;
		p.first = index / Width;
		p.second = index % Width;
		p.first += Hmin;
		p.second += Wmin;
		return p;
	}

private:
	T Hmax, Hmin, Wmax, Wmin;
	T Height, Width;
};

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

	const ll BIG = 100000000;

	int N, M; cin >> N >> M;
	vector<pair<int, int>> v;
	vector<string> S(N);
	for (int i = 0; i < N; i++) {
		cin >> S[i];
		for (int j = 0; j < M; j++) {
			if (S[i][j] == 'o') { v.push_back({ i,j }); }
		}
	}
	const int K = v.size();

	vector<vector<ll>> dp(N);
	for (int i = 0; i < N; i++) {
		dp[i].resize(M);
	}
	for (int i = N - 1; i >= 0; i--) {
		for (int j = M - 1; j >= 0; j--) {
			dp[i][j] = (N + M - 2) - i - j;
		}
	}

	HashMap2D<int> hm(N, M);
	const int s = N * M + K, t = N * M + K + 1;

	const ll cost_tran = 1;
	const ll cost_default = BIG * (N + M);

	mcf_graph<int, ll> G(N * M + K + 2);

	for (int i = 0; i < K; i++) {
		G.add_edge(s, N * M + i, 1, 0);
		G.add_edge(N * M + i, hm.hash(v[i].first, v[i].second), 1, 0);
	}
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M - 1; j++) {
			if (S[i][j] == '#' || S[i][j + 1] == '#') { continue; }
			G.add_edge(hm.hash(i, j), hm.hash(i, j + 1), K, 1);
		}
	}
	for (int i = 0; i < N - 1; i++) {
		for (int j = 0; j < M; j++) {
			if (S[i][j] == '#' || S[i + 1][j] == '#') { continue; }
			G.add_edge(hm.hash(i, j), hm.hash(i + 1, j), K, 1);
		}
	}
	for (int i = 0; i < N; i++) {
		for (int j = 0; j < M; j++) {
			if (S[i][j] == '#') { continue; }
			G.add_edge(hm.hash(i, j), t, 1, dp[i][j] * BIG);
		}
	}
	for (int i = 0; i < K; i++) {
		G.add_edge(s, t, K, BIG * (N + M));
	}

	auto result = G.flow(s, t, K);
	//cout << result.first << " " << result.second << endl;

	auto e = G.edges();

	for (int i = 0; i < e.size(); i++) {
		//cout << e[i].from << "->" << e[i].to << " : " << e[i].flow << "/" << e[i].cap << " * " << e[i].cost << endl;
	}
	
	cout << result.second % BIG << endl;

	return 0;
}

Measured memory_bytes: 4308