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 Java8 (OpenJDK 1.8.0) (judge verdict: Accepted)

import java.util.*;

class UnionFind {
	int[] roots;
	int[] sameSetCnt;
	
	public UnionFind(int N) {
		roots = new int[N];
		sameSetCnt = new int[N];
		for(int i = 0; i < N; ++i) {
			roots[i] = i;
			sameSetCnt[i] = 1;
		}
	}
	
	int getRoot(int x) {
		if(roots[x] == x) return x;
		else return roots[x] = getRoot(roots[x]);
	}
	
	boolean isSameSet(int x, int y) {
		return getRoot(x) == getRoot(y);
	}
	
	void unite(int x, int y) {
		int rx = getRoot(x);
		int ry = getRoot(y);
		if(rx == ry) return;
		
		if(sameSetCnt[rx] < sameSetCnt[ry]) {
			int tmp = rx;
			rx = ry;
			ry = tmp;
		}
		sameSetCnt[rx] += sameSetCnt[ry];
		roots[ry] = rx;
	}
	
	int getSameSetNum(int i) {
		return sameSetCnt[getRoot(i)];
	}
}

public class Main {
	static Scanner sc = new Scanner(System.in);
	static int h = 0;
	static int w = 0;
	static char[][] grids = null;
	static boolean[][] hasVisited = null;
	static int[][] bnum = null;
	static UnionFind uf = null;
	static int[] dy = {-1, 1, 0, 0};
	static int[] dx = {0, 0, -1, 1};
	
	public static void main(String[] args) {
		h = sc.nextInt();
		w = sc.nextInt();
		grids = new char[h][w];
		hasVisited = new boolean[h][w];
		for(int i = 0; i < h; ++i) grids[i] = sc.next().toCharArray();
		
		uf = new UnionFind(h * w);
		bnum = new int[h][w];
		// 黒と白が隣接する各箇所に辺を張る
		for(int y = 0; y < h; ++y) {
			for(int x = 0; x < w; ++x) {
				dfs(y, x);
			}
		}
		
		// 各連結成分について選ぶ組み合わせの数を足していく
		long ans = 0;
		for(int i = 0; i < h * w; ++i) {
			if(uf.getRoot(i) == i) {
				int bn = bnum[i / w][i - i / w * w];
				int wn = uf.getSameSetNum(i) - bn;
				ans += 1L * bn * wn;
			}
		}
		System.out.println(ans);
	}
	
	static boolean isOutside(int y, int x) {
		return y < 0 || h <= y || x < 0 || w <= x;
	}
	
	static void dfs(int sy, int sx) {
		// 訪問済みなら何もせず終了
		if(hasVisited[sy][sx]) return;
		
		// 自身のマスに訪問済みの印をつける
		hasVisited[sy][sx] = true;
		
		// 自身のマスの色
		char self = grids[sy][sx];
		
		// 黒マスならば連結成分の根のカウントを1増やす
		if(self == '#') {
			int ufRootIndex = uf.getRoot(w * sy + sx);
			++bnum[ufRootIndex / w][ufRootIndex - ufRootIndex / w * w];
		}
		
		// 上下左右のマスを見る
		for(int i = 0; i < 4; ++i) {
			int oy = sy + dy[i];
			int ox = sx + dx[i];
			
			// 範囲外ならばスキップ
			if(isOutside(oy, ox)) continue;
			
			// 相手のマスの色
			char other = grids[oy][ox];
			if(self != other) {
				// 自身と相手の色が異なれば辺を張る
				int selfIndex = w * sy + sx;
				int otherIndex = w * oy + ox;
				uf.unite(selfIndex, otherIndex);
				
				// 相手のマスに関してDFS
				dfs(oy, ox);
			}
		}
	}
}