Problem:
Score : 300 points

Problem Statement

There is a grid with H rows and W columns, where each square is painted black or white.

You are given H strings S_1, S_2, ..., S_H, each of length W.
If the square at the i-th row from the top and the j-th column from the left is painted black, the j-th character in the string S_i is #; if that square is painted white, the j-th character in the string S_i is ..

Find the number of pairs of a black square c_1 and a white square c_2 that satisfy the following condition:

There is a path from the square c_1 to the square c_2 where we repeatedly move to a vertically or horizontally adjacent square through an alternating sequence of black and white squares: black, white, black, white...

Constraints

1 \leq H, W \leq 400

|S_i| = W (1 \leq i \leq H)

For each i (1 \leq i \leq H), the string S_i consists of characters # and ..

Input

Input is given from Standard Input in the following format:

H W
S_1
S_2
:
S_H

Output

Print the answer.

Sample Input 1

3 3
.#.
..#
#..

Sample Output 1

10

Some of the pairs satisfying the condition are ((1, 2), (3, 3)) and ((3, 1), (3, 2)), where (i, j) denotes the square at the i-th row from the top and the j-th column from the left.

Sample Input 2

2 4
....
....

Sample Output 2

0

Sample Input 3

4 3
###
###
...
###

Sample Output 3

6

Solution in PyPy3 (2.4.0) (judge verdict: Accepted)

from itertools import groupby

class UnionFindVerSize():
    def __init__(self, N):
        global S,H,W
        """ N個のノードのUnion-Find木を作成する """
        # 親の番号を格納する。自分が親だった場合は、自分の番号になり、それがそのグループの番号になる
        self._parent = [n for n in range(0, N)]
        # グループのサイズ（個数）
        self._size = [1] * N

        self._black=[0]*N
        self._white=[0]*N

        for i in range(0,H*W):
            h=i//W
            w=i%W
            if S[h][w]=='#':
                self._black[i]=1
                self._white[i]=0
            else:
                self._black[i]=0
                self._white[i]=1

    def find_root(self, x):
        """ xの木の根(xがどのグループか)を求める """
        if self._parent[x] == x: return x
        self._parent[x] = self.find_root(self._parent[x]) # 縮約
        return self._parent[x]

    def unite(self, x, y):
        """ xとyの属するグループを併合する """
        gx = self.find_root(x)
        gy = self.find_root(y)
        if gx == gy: return

        # 小さい方を大きい方に併合させる（木の偏りが減るので）
        if self._size[gx] < self._size[gy]:
            self._parent[gx] = gy
            self._black[gy]+=self._black[gx]
            self._white[gy]+=self._white[gx]
            self._size[gy] += self._size[gx]
        else:
            self._parent[gy] = gx
            self._black[gx]+=self._black[gy]
            self._white[gx]+=self._white[gy]
            self._size[gx] += self._size[gy]

    def get_size(self, x):
        """ xが属するグループのサイズ（個数）を返す """
        return self._size[self.find_root(x)]

    def is_same_group(self, x, y):
        """ xとyが同じ集合に属するか否か """
        return self.find_root(x) == self.find_root(y)

    def calc_group_num(self):
        """ グループ数を計算して返す """
        N = len(self._parent)
        ans = 0
        for i in range(N):
            if self.find_root(i) == i:
                ans += 1
        return ans

    def calc_black(self,x):
        return self._black[self.find_root(x)]

    def calc_white(self,x):
        return self._white[self.find_root(x)]


H,W=map(int,input().split())
S=[]
for i in range(0,H):
    s=input()
    S.append(s)

uf=UnionFindVerSize(H*W)
for i in range(0,H*W):
    h=i//W
    w=i%W
    if W-1>=w+1:
        if S[h][w]!=S[h][w+1]:
            uf.unite(i,i+1)
    if H-1>=h+1:
        if S[h][w]!=S[h+1][w]:
            uf.unite(i,i+W)

root=[uf.find_root(i) for i in range(0,H*W)]
root.sort()

ans=0
root=groupby(root)
for key,group in root:
    black=uf.calc_black(key)
    white=uf.calc_white(key)
    ans+=black*white

print(ans)