// 最短路反復法
// src -> dst へflowだけ流せた時コストを返す
// 負閉路はないと仮定
// todo: SPFAじゃなくてポテンシャル付ダイクストラにする

const INF: i64 = 1_000_000_000_000_000i64 + 1;

struct Graph {
    size: usize,
    edge: Vec<(usize, usize, i64, i64)>,
}

impl Graph {
    fn new(size: usize) -> Self {
        Graph {
            size: size,
            edge: vec![],
        }
    }
    fn add_edge(&mut self, src: usize, dst: usize, capa: i64, cost: i64) {
        assert!(src < self.size && dst < self.size && src != dst);
        self.edge.push((src, dst, capa, cost));
    }
    fn solve(&self, src: usize, dst: usize, flow: i64) -> Option<i64> {
        if src == dst {
            return Some(0);
        }
        let size = self.size;
        let edge = &self.edge;
        let mut deg = vec![0; size];
        for &(a, b, _, _) in edge.iter() {
            deg[a] += 1;
            deg[b] += 1;
        }
        let mut graph: Vec<_> = deg.into_iter().map(|d| Vec::with_capacity(d)).collect();
        for &(a, b, capa, cost) in edge.iter() {
            let x = graph[a].len();
            let y = graph[b].len();
            graph[a].push((b, capa, cost, y));
            graph[b].push((a, 0, -cost, x));
        }
        let mut ans = 0;
        let mut dp = Vec::with_capacity(size);
        let mut elem = Vec::with_capacity(size);
        let mut que = std::collections::VecDeque::new();
        for _ in 0..flow {
            dp.clear();
            dp.resize(size, (INF, src, 0));// コスト、親、親からの番号
            dp[src] = (0, src, 0);
            elem.clear();
            elem.resize(size, false);
            elem[src] = true;
            que.push_back(src);
            while let Some(v) = que.pop_front() {
                elem[v] = false;
                let (c, _, _) = dp[v];
                for (i, &(u, capa, cost, _)) in graph[v].iter().enumerate() {
                    if capa == 0 {
                        continue;
                    }
                    let c = c + cost;
                    if c < dp[u].0 {
                        dp[u] = (c, v, i);
                        if !elem[u] {
                            elem[u] = true;
                            que.push_back(u);
                        }
                    }
                }
            }
            if dp[dst].0 == INF {
                return None;
            }
            ans += dp[dst].0;
            let mut pos = dst;
            while pos != src {
                let (_, parent, k) = dp[pos];
                let inv = graph[parent][k].3;
                graph[parent][k].1 -= 1;
                graph[pos][inv].1 += 1;
                pos = parent;
            }
        }
        Some(ans)
    }
}

use proconio::*;
use proconio::marker::*;
use std::collections::*;
use std::cmp::*;
use std::ops::Bound::*;

#[fastout]
fn run() {
    input! {
        h: usize,
        w: usize,
        s: [Bytes; h],
    }
    let mut g = Graph::new(h * w + 2);
    let src = h * w + 2 - 1;
    let dst = src - 1;
    let mut cnt = 0;
    for i in 0..h {
        for j in 0..w {
            let v = i * w + j;
            g.add_edge(v, dst, 1, 0);
            if s[i][j] == b'o' {
                cnt += 1;
                g.add_edge(src, v, 1, 0);
            }
        }
    }
    for i in 0..h {
        for j in 0..w {
            if s[i][j] == b'#' {
                continue;
            }
            if i + 1 < h && s[i + 1][j] != b'#' {
                g.add_edge(i * w + j, (i + 1) * w + j, cnt, -1);
            }
            if j + 1 < w && s[i][j + 1] != b'#' {
                g.add_edge(i * w + j, i * w + j + 1, cnt, -1);
            }
        }
    }
    let ans = -g.solve(src, dst, cnt).unwrap();
    println!("{}", ans);
}

fn main() {
    run();
}


Measured memory_bytes: 3004