Language: C++

#include <bits/stdc++.h>
#define all(a) (a).begin(), (a).end()
#define REP(i, n) for(int (i)=0; (i)<(n); (i)++)
#define RREP(i, n) for(int (i)=(n)-1, (i)>=0; (i)--)
#define ios() cin.tie(0), ios::sync_with_stdio(false)
typedef long long ll;
typedef unsigned long long ull;
ll const MOD = 1e9+7;
ll const INF = 1e18;
using namespace std;

//Union-Find Tree
class UnionFind{
    private:
        vector<vector<pair<int,int>>> par;    //par[i]:iの親の番号

    public:
        UnionFind(int h, int w) : par(h, vector<pair<int,int>>(w)){  //初期化時はすべて根とする
            REP(i, h){
                REP(j, w){
                    par[i][j] = {i, j};
                }
            }
        }
        
        pair<int,int> root(int x, int y){    //データxが属する木の根を再帰で得る
            if(par[x][y].first == x && par[x][y].second == y){
                return {x, y};
            }else{
                return par[x][y] = root(par[x][y].first, par[x][y].second); //経路圧縮を同時に実行
            }
        }

        void unite(int x1, int y1, int x2, int y2){   //xの属する木とyの属する木を併合
            pair<int,int> rx = root(x1, y1), ry = root(x2, y2);
            if(rx != ry){   //xとyの属する木が異なるとき
                par[x1][y1] = ry;    //xの木の根をyの根につける
            }
        }

        bool same(int x1, int y1, int x2, int y2){    //x,yが同じ木に属するときtrue, そうでなければfalse
            return root(x1, y1) == root(y1, y2);
        }

        void compression(){
            REP(i, h){
                REP(j, w){
                    par[x][y] = root(par[x][y].first, par[x][y].second);
                }
            }
        }
};

int main(){
    ios();
    int h, w;
    cin >> h >> w;
    vector<vector<char>> grid(h, vector<char>(w));
    UnionFind tree(h, w);
    REP(i, h){
        REP(j, w){
            cin >> grid[i][j];
        }
    }
    vector<pair<int,int>> dxy = {{-1,0}, {0,-1}, {1,0}, {0,1}};
    REP(i, h){
        REP(j, w){
            for(auto k : dxy){
                int x = i + k.first, y = j + k.second;
                if(x < 0 || x >= h || y < 0 || y >= w){
                    continue;
                }
                if(grid[i][j] != grid[x][y]){
                    tree.unite(i, j, x, y);
                }
            }
        }
    }
    tree.compression();
    vector<vector<pair<int,int>>> count(h, vector<pair<int,int>>(w));
    REP(i, h){
        REP(j, w){
            auto p = tree.root(i, j);
            if(grid[i][j] == '#'){
                count[p.first][p.second].first++;
            }else{
                count[p.first][p.second].second++;
            }
        }
    }
    int ans = 0;
    REP(i, h){
        REP(j, w){
            ans += count[i][j].first * count[i][j].second;
        }
    }
    cout << ans << endl;
    return 0;
}

Compiler output:
a.cc: In member function 'void UnionFind::compression()':
a.cc:46:20: error: 'h' was not declared in this scope
   46 |             REP(i, h){
      |                    ^
a.cc:3:39: note: in definition of macro 'REP'
    3 | #define REP(i, n) for(int (i)=0; (i)<(n); (i)++)
      |                                       ^
a.cc:47:24: error: 'w' was not declared in this scope
   47 |                 REP(j, w){
      |                        ^
a.cc:3:39: note: in definition of macro 'REP'
    3 | #define REP(i, n) for(int (i)=0; (i)<(n); (i)++)
      |                                       ^
a.cc:48:25: error: 'x' was not declared in this scope
   48 |                     par[x][y] = root(par[x][y].first, par[x][y].second);
      |                         ^
a.cc:48:28: error: 'y' was not declared in this scope; did you mean 'yn'?
   48 |                     par[x][y] = root(par[x][y].first, par[x][y].second);
      |                            ^
      |                            yn