import core.bitop;
import std.algorithm;
import std.ascii;
import std.bigint;
import std.conv;
import std.functional;
import std.math;
import std.numeric;
import std.range;
import std.stdio;
import std.string;
import std.random;
import std.typecons;

alias sread = () => readln.chomp();
alias Point2 = Tuple!(long, "y", long, "x");

T lread(T = long)()
{
    return readln.chomp.to!T();
}

T[] aryread(T = long)()
{
    return readln.split.to!(T[])();
}

void scan(TList...)(ref TList Args)
{
    auto line = readln.split();
    foreach (i, T; TList)
    {
        T val = line[i].to!(T);
        Args[i] = val;
    }
}

void minAssign(T, U = T)(ref T dst, U src)
{
    dst = cast(T) min(dst, src);
}

void maxAssign(T, U = T)(ref T dst, U src)
{
    dst = cast(T) max(dst, src);
}

string[] M;
long H, W;
bool[][] used;
void main()
{
    scan(H, W);
    used = new bool[][](H, W);
    foreach (_; 0 .. H)
        M ~= readln().chomp;

    long sum;
    foreach (y; 0 .. H)
        foreach (x; 0 .. W)
        {
            auto t = dfs(y, x);
            sum += t[0] * t[1];
        }
    writeln(sum);
}

Tuple!(long, long) dfs(long y, long x)
{
    if (used[y][x])
        return Tuple!(long, long)(0, 0);
    used[y][x] = true;

    long w, b;
    if (M[y][x] == '.')
        w = 1;
    else
        b = 1;

    if (0 <= y - 1 && !used[y - 1][x] && M[y][x] != M[y - 1][x])
    {
        auto tmp = dfs(y - 1, x);
        w += tmp[0];
        b += tmp[1];
    }
    if (y + 1 < H && !used[y + 1][x] && M[y][x] != M[y + 1][x])
    {
        auto tmp = dfs(y + 1, x);
        w += tmp[0];
        b += tmp[1];
    }
    if (0 <= x - 1 && !used[y][x - 1] && M[y][x] != M[y][x - 1])
    {
        auto tmp = dfs(y, x - 1);
        w += tmp[0];
        b += tmp[1];
    }
    if (x + 1 < W && !used[y][x + 1] && M[y][x] != M[y][x + 1])
    {
        auto tmp = dfs(y, x + 1);
        w += tmp[0];
        b += tmp[1];
    }
    return Tuple!(long, long)(w, b);
}
