{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ExplicitForAll #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE Unsafe #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE FlexibleContexts #-}

import Data.Bits
import Data.List
import qualified Data.ByteString.Char8 as BS
import qualified Data.ByteString.Lazy.Char8 as BSL
import Data.IntMap.Strict (IntMap)
import qualified Data.IntMap.Strict as IMS
import Data.IntSet (IntSet)
import qualified Data.IntSet as IS
import qualified Data.Sequence as Seq
import Data.Sequence (Seq)
import qualified Data.Array.IArray as A
import qualified Data.Array.MArray.Safe as A
import qualified Data.Array.MArray as A
import Data.Array (Array)
import Data.Array.Unboxed (UArray)
import Data.Array.IArray (IArray)
import Data.Array.MArray.Safe (MArray)
import Data.Array.IO.Safe (IOArray, IOUArray)
import Data.Array.ST.Safe (STArray, STUArray, runSTArray, runSTUArray)
import qualified Data.Vector as V
import qualified Data.Vector.Unboxed as VU
import qualified Data.Vector.Unboxed.Mutable as VUM
import Control.Monad
import Control.Monad.ST
-- import Control.Monad.ST.Safe
import Control.Applicative
import Data.Maybe
import Data.Tuple
import Data.Ord
import Control.Monad.State.Strict
import Data.Int
import System.IO
import Data.Function
import Control.Monad.Primitive
import qualified Data.Vector.Primitive.Mutable as VPM
import qualified Data.Vector.Generic.Mutable as VGM
import Data.STRef
import Data.IORef

main :: IO ()
main = do
  [h,w] <- map read . words <$> getLine :: IO [Int]
  vs <- VU.map (== '#') . VU.filter (>='!') . VU.unfoldrN (h*(w+1)) BSL.uncons
        <$> BSL.getContents
  print $ query h w vs

query :: Int -> Int -> VU.Vector Bool -> Int
query h w vs = VU.sum $ VU.map (\(_,_,b,w) -> b*w)
  $ VU.filter (\(isRoot,_,_,_) -> isRoot) uf
  where
    uf :: VU.Vector (Bool,Int,Int,Int)
    uf = VU.create $ do
      uf <- VU.thaw $ VU.map (\c -> if c then (True,0,1,0) else (True,0,0,1))
                       vs
      forM_ [0,w..(h-1)*w] $ \yw ->
        forM_ [yw..yw+w-2] $ \l ->
          when (vs VU.! l /= vs VU.! (l+1)) $ unionUF uf l (l+1)
      forM_ [0..(h-1)*w-1] $ \u ->
        when (vs VU.! u /= vs VU.! (u+w)) $ unionUF uf u (u+w)
      return uf



type UFForest s = VUM.MVector s (Bool -- if root, True
                                ,Int  -- parent or rank
                                ,Int  -- blacks '#' if root
                                ,Int  -- whites '.' if root
                                )

findUF :: UFForest s -> Int -> ST s (Int, Int, Int, Int)
findUF forest ident =
  do
   (isRoot, par, b, w) <- VUM.read forest ident
   if isRoot then
     return (ident,par,b,w)
   else do
     (root,rank,b,w) <- findUF forest par
     when (par /= root) $ VUM.write forest ident (False, root, b, w)
     return (root,rank,b,w)

unionUF :: UFForest s -> Int -> Int -> ST s ()
unionUF forest ident1 ident2 =
  do
    (root1, rank1, b1, w1) <- findUF forest ident1
    (root2, rank2, b2, w2) <- findUF forest ident2
    let (root,child) | rank1 >= rank2 = (root1,root2)
                     | otherwise      = (root2,root1)
        rank | rank1 == rank2 = rank1 + 1
             | otherwise      = max rank1 rank2
        b = b1 + b2
        w = w1 + w2
    when (root1 /= root2) $ do
      VUM.write forest root  (True, rank, b, w)
      VUM.write forest child (False, root, b, w)


Measured memory_bytes: 7676