#![allow(unused_imports)]
use std::cmp::*;
use std::collections::*;
use std::io::Write;
use std::ops::Bound::*;

#[allow(unused_macros)]
macro_rules! debug {
    ($($e:expr),*) => {
        #[cfg(debug_assertions)]
        $({
            let (e, mut err) = (stringify!($e), std::io::stderr());
            writeln!(err, "{} = {:?}", e, $e).unwrap()
        })*
    };
}

fn exit_index(pones: &Vec<(usize, usize)>, x: usize, y: usize, w: usize) -> usize {
    pones.len() + x + y * w
}

fn main() {
    let v = read_vec::<usize>();
    let (h, w) = (v[0], v[1]);
    let mut grid = vec![vec![]; h];
    for i in 0..h {
        grid[i] = read::<String>().chars().collect::<Vec<_>>();
    }

    let mut pones = vec![];
    for y in 0..h {
        for x in 0..w {
            if grid[y][x] == 'o' {
                pones.push((x, y));
            }
        }
    }

    let exit_size = h * w;

    let inf = 1000000000i64;
    let mut flow = MinCostFlowGraph::<i64>::new(pones.len() + exit_size + 2);
    // [0 - pones.len()): pones
    // [pones.len(), h * w): exits
    let s = pones.len() + exit_size;
    let t = s + 1;

    for i in 0..pones.len() {
        flow.add_edge(s, i, 1, 0);
    }
    for y in 0..h {
        for x in 0..w {
            if grid[y][x] == '#' {
                continue;
            }
            flow.add_edge(exit_index(&pones, x, y, w), t, 1, 0);
        }
    }
    for (i, &(start_x, start_y)) in pones.iter().enumerate() {
        let mut reachable = vec![vec![false; w]; h];
        reachable[start_y][start_x] = true;
        for y in start_y..h {
            for x in start_x..w {
                if grid[y][x] == '#' {
                    continue;
                }
                if (y > 0 && reachable[y - 1][x]) || (x > 0 && reachable[y][x - 1]) {
                    reachable[y][x] = true;
                }
                if reachable[y][x] {
                    let manhatten_dist = (y - start_y + x - start_x) as i64;
                    flow.add_edge(i, exit_index(&pones, x, y, w), 1, inf - manhatten_dist);
                }
            }
        }
    }
    let (max_flow, min_cost) = flow.flow(s, t, pones.len() as i64);
    println!("{}", inf * max_flow as i64 - min_cost);
}

fn read<T: std::str::FromStr>() -> T {
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).ok();
    s.trim().parse().ok().unwrap()
}

fn read_vec<T: std::str::FromStr>() -> Vec<T> {
    read::<String>()
        .split_whitespace()
        .map(|e| e.parse().ok().unwrap())
        .collect()
}

use std::{
    fmt,
    iter::{Product, Sum},
    ops::{
        Add, AddAssign, BitAnd, BitAndAssign, BitOr, BitOrAssign, BitXor, BitXorAssign, Div,
        DivAssign, Mul, MulAssign, Not, Rem, RemAssign, Shl, ShlAssign, Shr, ShrAssign, Sub,
        SubAssign,
    },
};

pub trait Integral:
    'static
    + Send
    + Sync
    + Copy
    + Ord
    + Not<Output = Self>
    + Add<Output = Self>
    + Sub<Output = Self>
    + Mul<Output = Self>
    + Div<Output = Self>
    + Rem<Output = Self>
    + AddAssign
    + SubAssign
    + MulAssign
    + DivAssign
    + RemAssign
    + Sum
    + Product
    + BitOr<Output = Self>
    + BitAnd<Output = Self>
    + BitXor<Output = Self>
    + BitOrAssign
    + BitAndAssign
    + BitXorAssign
    + Shl<Output = Self>
    + Shr<Output = Self>
    + ShlAssign
    + ShrAssign
    + fmt::Display
    + fmt::Debug
    + fmt::Binary
    + fmt::Octal
{
    fn zero() -> Self;
    fn one() -> Self;
    fn min_value() -> Self;
    fn max_value() -> Self;
}

macro_rules! impl_integral {
    ($($ty:ty),*) => {
        $(
            impl Integral for $ty {
                #[inline]
                fn zero() -> Self {
                    0
                }

                #[inline]
                fn one() -> Self {
                    1
                }

                #[inline]
                fn min_value() -> Self {
                    Self::min_value()
                }

                #[inline]
                fn max_value() -> Self {
                    Self::max_value()
                }
            }
        )*
    };
}

impl_integral!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize);

#[derive(Clone)]
pub struct MinCostFlowEdge<T: Clone> {
    pub from: usize,
    pub to: usize,
    pub cap: T,
    pub flow: T,
    pub cost: T,
}

#[derive(Clone)]
pub struct MinCostFlowGraph<T: Clone> {
    pos: Vec<(usize, usize)>,
    g: Vec<Vec<_Edge<T>>>,
    cost_sum: T,
}

impl<T> MinCostFlowGraph<T>
where
    T: Integral + std::ops::Neg<Output = T> + Clone,
{
    pub fn new(n: usize) -> Self {
        Self {
            pos: vec![],
            g: (0..n).map(|_| vec![]).collect(),
            cost_sum: T::zero(),
        }
    }

    pub fn get_edge(&self, i: usize) -> MinCostFlowEdge<T> {
        assert!(i < self.pos.len());
        let e = &self.g[self.pos[i].0][self.pos[i].1];
        let re = &self.g[e.to][e.rev];
        MinCostFlowEdge {
            from: self.pos[i].0,
            to: e.to,
            cap: e.cap,
            flow: re.cap,
            cost: e.cost,
        }
    }

    pub fn edges(&self) -> Vec<MinCostFlowEdge<T>> {
        let m = self.pos.len();
        let mut result = vec![];
        for i in 0..m {
            result.push(self.get_edge(i));
        }
        result
    }

    pub fn add_edge(&mut self, from: usize, to: usize, cap: T, cost: T) -> usize {
        assert!(from < self.g.len());
        assert!(to < self.g.len());
        assert_ne!(from, to);
        assert!(cap >= T::zero());
        assert!(cost >= T::zero());

        self.pos.push((from, self.g[from].len()));
        self.cost_sum += cost;

        let rev = self.g[to].len();
        self.g[from].push(_Edge { to, rev, cap, cost });

        let rev = self.g[from].len() - 1;
        self.g[to].push(_Edge {
            to: from,
            rev,
            cap: T::zero(),
            cost: -cost,
        });

        self.pos.len() - 1
    }

    /// Returns (maximum flow, cost)
    pub fn flow(&mut self, source: usize, sink: usize, flow_limit: T) -> (T, T) {
        self.slope(source, sink, flow_limit).pop().unwrap()
    }

    pub fn slope(&mut self, source: usize, sink: usize, flow_limit: T) -> Vec<(T, T)> {
        let n = self.g.len();
        assert!(source < n);
        assert!(sink < n);
        assert_ne!(source, sink);

        let mut dual = vec![T::zero(); n];
        let mut dist = vec![self.cost_sum; n];
        let mut vis = vec![false; n];
        let mut prev_v = vec![0; n];
        let mut prev_e = vec![0; n];
        let mut flow = T::zero();
        let mut cost = T::zero();
        let mut prev_cost: Option<T> = None;
        let mut result = vec![(flow, cost)];
        while flow < flow_limit {
            if !self.refine_dual(
                source,
                sink,
                &mut dual,
                &mut prev_v,
                &mut prev_e,
                &mut dist,
                &mut vis,
            ) {
                break;
            }

            let mut c = flow_limit - flow;
            let mut v = sink;
            while v != source {
                c = std::cmp::min(c, self.g[prev_v[v]][prev_e[v]].cap);
                v = prev_v[v];
            }

            let mut v = sink;
            while v != source {
                self.g[prev_v[v]][prev_e[v]].cap -= c;
                let rev = self.g[prev_v[v]][prev_e[v]].rev;
                self.g[v][rev].cap += c;
                v = prev_v[v];
            }

            let d = -dual[source];
            flow += c;
            cost += d * c;
            if prev_cost == Some(d) {
                assert!(result.pop().is_some());
            }
            result.push((flow, cost));
            prev_cost = Some(cost);
        }
        result
    }

    fn refine_dual(
        &self,
        source: usize,
        sink: usize,
        dual: &mut [T],
        pv: &mut [usize],
        pe: &mut [usize],
        dist: &mut [T],
        vis: &mut [bool],
    ) -> bool {
        let n = self.g.len();
        dist.iter_mut().for_each(|d| *d = self.cost_sum);
        vis.iter_mut().for_each(|v| *v = false);

        let mut que = std::collections::BinaryHeap::new();
        dist[source] = T::zero();
        que.push((std::cmp::Reverse(T::zero()), source));
        while let Some((_, v)) = que.pop() {
            if vis[v] {
                continue;
            }
            vis[v] = true;
            if v == sink {
                break;
            }

            for (i, e) in self.g[v].iter().enumerate() {
                if vis[e.to] || e.cap == T::zero() {
                    continue;
                }

                let cost = e.cost - dual[e.to] + dual[v];
                if dist[e.to] - dist[v] > cost {
                    dist[e.to] = dist[v] + cost;
                    pv[e.to] = v;
                    pe[e.to] = i;
                    que.push((std::cmp::Reverse(dist[e.to]), e.to));
                }
            }
        }

        if !vis[sink] {
            return false;
        }

        for v in 0..n {
            if !vis[v] {
                continue;
            }
            dual[v] -= dist[sink] - dist[v];
        }
        true
    }
}
#[derive(Clone)]
struct _Edge<T: Clone> {
    to: usize,
    rev: usize,
    cap: T,
    cost: T,
}


Measured memory_bytes: 9664