#include <bits/stdc++.h>
using namespace std;

#define CPP_STR(x) CPP_STR_I(x)
#define CPP_CAT(x,y) CPP_CAT_I(x,y)
#define CPP_STR_I(args...) #args
#define CPP_CAT_I(x,y) x ## y

using i8  = int8_t;
using u8  = uint8_t;
using i16 = int16_t;
using u16 = uint16_t;
using i32 = int32_t;
using u32 = uint32_t;
using i64 = int64_t;
using u64 = uint64_t;

using f32 = float;
using f64 = double;

#define FOR(i, start, end) for(i64 i = (start), CPP_CAT(i,xxxx_end)=(end); i < CPP_CAT(i,xxxx_end); ++i)
#define REP(i, n) FOR(i, 0, n)
#define REPS(i, n) for(i64 i = 1; i <= n; ++i)
#define RREP(i, n) for(i64 i = n - 1; i >= 0 ; --i)
#define RREPS(i, n) for(i64 i = n; i >= 1; --i)

#define ALL(f,c,...) (([&](decltype((c)) cccc) { return (f)(std::begin(cccc), std::end(cccc), ## __VA_ARGS__); })(c))

#define UNIQUE(v) v.erase( unique(v.begin(), v.end()), v.end() );

#define pb push_back
#define mp make_pair

template<class T>bool chmax(T &a, const T &b) { if (a<b) { a=b; return 1; } return 0; }
template<class T>bool chmin(T &a, const T &b) { if (b<a) { a=b; return 1; } return 0; }

template<typename C>
i64 SIZE(const C& c) { return static_cast<i64>(c.size()); }

template<typename T, size_t N>
i64 SIZE(const T (&)[N]) { return static_cast<i64>(N); }

#define YES(n) cout << ((n) ? "YES" : "NO"  ) << endl
#define Yes(n) cout << ((n) ? "Yes" : "No"  ) << endl
#define POSSIBLE(n) cout << ((n) ? "POSSIBLE" : "IMPOSSIBLE"  ) << endl
#define Possible(n) cout << ((n) ? "Possible" : "Impossible"  ) << endl

#define VIN(x) for(auto&youso_: (x) )cin>>youso_
#define VOUT(x, delim) for(auto&youso_: (x) )cout<<youso_<<delim;cout<<endl

#define COUT(x) cout<<(x)<<endl
void CINT(){}
template <class Head,class... Tail>
void CINT(Head&& head,Tail&&... tail){
    cin>>head;
    CINT(move(tail)...);
}
#define CIN(...) i64 __VA_ARGS__;CINT(__VA_ARGS__)
#define SCIN(...) string __VA_ARGS__;CINT(__VA_ARGS__)


struct ProconInit {
    static constexpr int IOS_PREC = 15;
    static constexpr bool AUTOFLUSH = false;

    ProconInit() {
        cin.tie(nullptr);
        ios::sync_with_stdio(false);
        cout << fixed << setprecision(IOS_PREC);
        if(AUTOFLUSH)
            cout << unitbuf;
    }
} PROCON_INIT;

template<typename T>
struct UnionFind {
    i64 sz;

    vector<T> parent;
    vector<i64> rank;

    UnionFind(i64 n) : sz(n) {
        parent.assign(n, 0);
        REP(i, n) {
            parent[i] = i;
        }
        rank.assign(n, 1);
    }

    T find(T t) {
        if (parent[t] == t) return t;
        return parent[t] = find(parent[t]);
    }

    bool unite(T t1, T t2) {
        t1 = find(t1);
        t2 = find(t2);
        if (t1 == t2) return false;

        if (rank[t1] < rank[t2]) swap(t1, t2);
        parent[t2] = t1;
        rank[t1] += rank[t2];
        return true;
    }

    bool same(T t1, T t2) {
        return find(t1) == find(t2);
    }
};

i64 dy[4] = {-1, 0, 1, 0};
i64 dx[4] = {0, -1, 0, 1};

using P = pair<i64, i64>;

int main() {
    CIN(H, W);
    vector<string> G(H);
    VIN(G);

    auto id2pos = [&](i64 i) {
        return P(i % W, i / W);
    };
    auto pos2id = [&](i64 y, i64 x) {
        return y * W + x;
    };

    UnionFind<i64> uf(H * W);

    REP(y, H) {
        REP(x, W) {
            REP(i, 4) {
                i64 ny = y + dy[i];
                i64 nx = x + dx[i];
                if (ny < 0 || H <= ny || nx < 0 || W <= nx) continue;
                if (G[y][x] == G[ny][nx]) continue;
                uf.unite(pos2id(y, x), pos2id(ny, nx));
            }
        }
    }

    map<i64, P> comp;
    REP(y, H) {
        REP(x, W) {
            i64 p = uf.find(pos2id(y, x));
            if (G[y][x] == '#') comp[p].first++;
            else comp[p].second++;
        }
    }

    i64 cnt = 0;
    for (auto [_, p] : comp) {
        cnt += p.first * p.second;
    }
    COUT(cnt);

    return 0;
}


Measured memory_bytes: 14236