Buggy python submission (verdict: Time Limit Exceeded):
import sys
from collections import deque


def input():
    return sys.stdin.readline().strip()


sys.setrecursionlimit(20000000)

MOD = 10 ** 9 + 7
INF = float("inf")


class UnionFind:
    # 初期化
    def __init__(self, n_nodes):
        self.n_nodes = n_nodes
        # 親要素のノード番号を格納する.初めは全て親ノード.
        # self.parent[x] == x の時，そのノードは根.
        self.parent = [i for i in range(n_nodes)]
        # 木の高さを格納する．
        self.rank = [1] * n_nodes
        # 自分と同じグループの要素数を格納．親要素を参照するようにする
        self.size = [1] * n_nodes

    # 検索. 根を返す.
    def find(self, x):
        if self.parent[x] == x:
            return x
        else:
            # 一度調べた値は，根に繋ぎ直す.
            self.parent[x] = self.find(self.parent[x])
            return self.parent[x]

    # 併合.
    # 木の高さが低くなるように.
    # 親要素の書き換えは少ない方がいい
    def unite(self, x, y):
        # 根を探す．
        x = self.find(x)
        y = self.find(y)
        # 根が同じ場合はそのまま
        if x == y:
            return
        # 木の高さを比較し，低い方を高い方の根に貼る.
        if self.rank[x] > self.rank[y]:
            self.parent[y] = x
            self.size[x] += self.size[y]
        else:
            self.parent[x] = y
            self.size[y] += self.size[x]
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1

    # 同じ集合に属するか判定
    def check(self, x, y):
        return self.find(x) == self.find(y)

    # 同一グループに所属する要素数を返す
    def get_size(self, x):
        return self.size[self.find(x)]

    # 親要素のリストを返す
    def get_parent_list(self):
        return [i for i in range(self.n_nodes) if self.find(i) == i]

    # グループの数を返す
    def get_n_groups(self):
        return len(self.get_parent_list())

    # 同じグループに所属する要素の集合を返す
    def get_members(self, x):
        parent = self.find(x)
        return [i for i in range(self.n_nodes) if self.find(i) == parent]

    # 全ての根に対する，グループのメンバーを返す．
    def get_members_dict(self):
        return {par: self.get_members(par) for par in self.get_parent_list()}


def main():
    H, W = map(int, input().split())
    S = [list(input()) for _ in range(H)]
    UF = UnionFind(H * W)
    for h in range(H):
        for w in range(W):
            if S[h][w] == "#":
                for x, y in [(-1, 0), (0, -1), (1, 0), (0, 1)]:
                    if 0 <= h + x < H and 0 <= w + y < W:
                        if S[h + x][w + y] == ".":
                            UF.unite(h * W + w, (h + x) * W + w + y)
    members = UF.get_members_dict()
    parent = UF.get_parent_list()
    answer = 0
    for p in parent:
        member = members[p]
        black = 0
        white = 0
        for m in member:
            w = m % W
            h = m // W
            if S[h][w] == "#":
                black += 1
            else:
                white += 1
        answer += black * white
    print(answer)


if __name__ == "__main__":
    main()


Fixed submission:
import sys
from collections import deque


def input():
    return sys.stdin.readline().strip()


sys.setrecursionlimit(20000000)

MOD = 10 ** 9 + 7
INF = float("inf")


def dfs(maze, H, W, sh, sw, gh, gw):
    visited = [[False] * W for _ in range(H)]
    visited[sh][sw] = True
    q = deque([(sh, sw)])
    while q:
        h, w = q.pop()
        if h == gh and w == gw:
            return True
        for dh, dw in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
            nh = h + dh
            nw = w + dw
            if 0 <= nh < H and 0 <= nw < W:
                if not visited[nh][nw]:
                    if maze[nh][nw] == ".":
                        visited[nh][nw] = True
                        q.append((nh, nw))
    return False


def main():
    H, W = map(int, input().split())
    S = [list(input()) for _ in range(H)]
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]
    seen = [[False] * W for _ in range(H)]

    def dfs(x, y, black, white):
        q = deque([(x, y)])
        while q:
            X, Y = q.pop()
            if seen[X][Y]:
                continue
            seen[X][Y] = True
            if S[X][Y] == "#":
                black += 1
            else:
                white += 1
            for i in range(4):
                nx = X + dx[i]
                ny = Y + dy[i]
                if not (0 <= nx < H and 0 <= ny < W):
                    continue
                if S[X][Y] == S[nx][ny]:
                    continue
                if seen[nx][ny]:
                    continue
                q.append((nx, ny))
        return black, white

    answer = 0
    for h in range(H):
        for w in range(W):
            if S[h][w] == ".":
                continue
            if seen[h][w]:
                continue
            black = 0
            white = 0
            black, white = dfs(h, w, black, white)
            answer += black * white

    print(answer)


if __name__ == "__main__":
    main()

Buggy python submission (verdict: Time Limit Exceeded):
import sys
from collections import deque


def input():
    return sys.stdin.readline().strip()


sys.setrecursionlimit(20000000)

MOD = 10 ** 9 + 7
INF = float("inf")


class UnionFind:
    # 初期化
    def __init__(self, n_nodes):
        self.n_nodes = n_nodes
        # 親要素のノード番号を格納する.初めは全て親ノード.
        # self.parent[x] == x の時，そのノードは根.
        self.parent = [i for i in range(n_nodes)]
        # 木の高さを格納する．
        self.rank = [1] * n_nodes
        # 自分と同じグループの要素数を格納．親要素を参照するようにする
        self.size = [1] * n_nodes

    # 検索. 根を返す.
    def find(self, x):
        if self.parent[x] == x:
            return x
        else:
            # 一度調べた値は，根に繋ぎ直す.
            self.parent[x] = self.find(self.parent[x])
            return self.parent[x]

    # 併合.
    # 木の高さが低くなるように.
    # 親要素の書き換えは少ない方がいい
    def unite(self, x, y):
        # 根を探す．
        x = self.find(x)
        y = self.find(y)
        # 根が同じ場合はそのまま
        if x == y:
            return
        # 木の高さを比較し，低い方を高い方の根に貼る.
        if self.rank[x] > self.rank[y]:
            self.parent[y] = x
            self.size[x] += self.size[y]
        else:
            self.parent[x] = y
            self.size[y] += self.size[x]
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1

    # 同じ集合に属するか判定
    def check(self, x, y):
        return self.find(x) == self.find(y)

    # 同一グループに所属する要素数を返す
    def get_size(self, x):
        return self.size[self.find(x)]

    # 親要素のリストを返す
    def get_parent_list(self):
        return [i for i in range(self.n_nodes) if self.find(i) == i]

    # グループの数を返す
    def get_n_groups(self):
        return len(self.get_parent_list())

    # 同じグループに所属する要素の集合を返す
    def get_members(self, x):
        parent = self.find(x)
        return [i for i in range(self.n_nodes) if self.find(i) == parent]

    # 全ての根に対する，グループのメンバーを返す．
    def get_members_dict(self):
        return {par: self.get_members(par) for par in self.get_parent_list()}


def main():
    H, W = map(int, input().split())
    S = [list(input()) for _ in range(H)]
    UF = UnionFind(H * W)
    for h in range(H):
        for w in range(W):
            if S[h][w] == "#":
                for x, y in [(-1, 0), (0, -1), (1, 0), (0, 1)]:
                    if 0 <= h + x < H and 0 <= w + y < W:
                        if S[h + x][w + y] == ".":
                            UF.unite(h * W + w, (h + x) * W + w + y)
    members = UF.get_members_dict()
    parent = UF.get_parent_list()
    answer = 0
    for p in parent:
        member = members[p]
        black = 0
        white = 0
        for m in member:
            w = m % W
            h = m // W
            if S[h][w] == "#":
                black += 1
            else:
                white += 1
        answer += black * white
    print(answer)


if __name__ == "__main__":
    main()


Fixed submission:
import sys
from collections import deque


def input():
    return sys.stdin.readline().strip()


sys.setrecursionlimit(20000000)

MOD = 10 ** 9 + 7
INF = float("inf")


def dfs(maze, H, W, sh, sw, gh, gw):
    visited = [[False] * W for _ in range(H)]
    visited[sh][sw] = True
    q = deque([(sh, sw)])
    while q:
        h, w = q.pop()
        if h == gh and w == gw:
            return True
        for dh, dw in [(1, 0), (-1, 0), (0, -1), (0, 1)]:
            nh = h + dh
            nw = w + dw
            if 0 <= nh < H and 0 <= nw < W:
                if not visited[nh][nw]:
                    if maze[nh][nw] == ".":
                        visited[nh][nw] = True
                        q.append((nh, nw))
    return False


def main():
    H, W = map(int, input().split())
    S = [list(input()) for _ in range(H)]
    dx = [1, 0, -1, 0]
    dy = [0, 1, 0, -1]
    seen = [[False] * W for _ in range(H)]

    def dfs(x, y, black, white):
        q = deque([(x, y)])
        while q:
            X, Y = q.pop()
            if seen[X][Y]:
                continue
            seen[X][Y] = True
            if S[X][Y] == "#":
                black += 1
            else:
                white += 1
            for i in range(4):
                nx = X + dx[i]
                ny = Y + dy[i]
                if not (0 <= nx < H and 0 <= ny < W):
                    continue
                if S[X][Y] == S[nx][ny]:
                    continue
                if seen[nx][ny]:
                    continue
                q.append((nx, ny))
        return black, white

    answer = 0
    for h in range(H):
        for w in range(W):
            if S[h][w] == ".":
                continue
            if seen[h][w]:
                continue
            black = 0
            white = 0
            black, white = dfs(h, w, black, white)
            answer += black * white

    print(answer)


if __name__ == "__main__":
    main()