#include <iostream>
#include <vector>
#include <deque>
#include <tuple>
#include <unordered_set>

using namespace std;

struct Result
{
    unsigned long Border;
    unsigned long Rperimeter;
    unsigned long Fperimeter;
};

// Utility class
class Task120
{
protected:
    const vector<string>& Field;
    const unsigned long  Width;
    const unsigned long  Height;

protected:

    unsigned int IsEnemy(int i, int j, char type)
    {
        if (i >=0 && i < Height && j >= 0 && j < Width) {
            return (Field[i][j] != type ? 1 : 0);
        }

        return 0;
    }

    unsigned int OuterCount(int i, int j)
    {
        return
            (unsigned int)(i == 0) +
            (unsigned int)(j == 0) +
            (unsigned int)(i == Height-1) +
            (unsigned int)(j == Width-1);
    }

public:
    Task120(const vector<string>& field)
        : Field(field)
        , Width(field[0].size())
        , Height(field.size())
    {}

    virtual Result solve() = 0;
};

// Простое решение за O(N^2) . Посещаем все ячейки и для каждой ячейки
// отмечаем принадлежит ли она периметру и/или линии фронта. Естественно
// учитываем сколько граней отвечают этим условиям. Дальше - простая арифметика
class Task120_Quadratic : public Task120
{
public:
    Task120_Quadratic(const vector<string>& field)
        : Task120(field)
    {}

    Result solve() override
    {
        unsigned long  border = 0, outer = 0;
        char type = Field[0][0];

        for (int i = 0; i < Height; ++i) {
            for (int j = 0; j < Width; ++j) {
                if (Field[i][j] == type) {
                    outer += OuterCount(i, j);
                    border +=
                        IsEnemy(i - 1, j, Field[i][j]) +
                        IsEnemy(i + 1, j, Field[i][j]) +
                        IsEnemy(i, j - 1, Field[i][j]) +
                        IsEnemy(i, j + 1, Field[i][j]);
                }
            }
        }

        const auto total = 2*(Width + Height);

        if (type == 'R') {
            return {border, outer + border, (total - outer) + border};
        } else {
            return {border, (total - outer) + border , outer + border};
        }

    }
};


struct PairHash
{
    size_t operator ()(const pair<int,int>& p) const
    {
        return (unsigned int)p.first * 1000 + (unsigned int)p.second;
    }
};

// Чуть более сложное, но и потенциально более быстрое решение. Можно заметить, что
// если линия фронта пересекает поле от грани до грани то ее длина вполне может
// быть линейной. Во всяком случае в предыдущем решении мы просматривали точки которые
// однозначно не могли нас заинтересовать (внутренние точки областей). Было бы хорошо если
// алгоритм старался следовать границе между областями и просматривал бы только точки на линии фронта
// или точки принадлежащие только периметру. Собственно мы сдесь это и делаем. Начинаем с точки (0,0)
// т.к она точно нас интересует (принадлежит периметру) и используя очередь "интересных вершин" вдоль
// периметра и линии фронта.
// Тут есть один вырожденный случай. Это когда одна область полностью лежит внутри другой ("котел","бублик").
// Очевидно что в таком случае алгоритм не найдет линию фронта. В этом случае приходится в массиве найти первую
// точку принадлежащию противнику и повторить алгоритм в этой точке (эта точка будет граничной).
// После чего результаты скомпоновать.
// Сложность O(N^2) в худшем случае, но есть подозрение что где-то в среднем он O(n+m)

class Task120_Opt : public Task120
{
private:
    unordered_set<pair<int,int>, PairHash> visited;
    unsigned long  border = 0, outer = 0;

private:

    bool IsOutOfBounds(int i, int j) {
        return
        i < 0 ||
        j < 0 ||
        i >= Height ||
        j >= Width;
    }

    bool IsSuitable(int i, int j, char type, unsigned int& b, unsigned int& o)
    {
        if (!IsOutOfBounds(i,j) && Field[i][j] == type && !visited.count(make_pair(i, j))) {
            b = IsEnemy(i-1, j, Field[i][j]) +
                          IsEnemy(i+1, j, Field[i][j]) +
                          IsEnemy(i, j-1, Field[i][j]) +
                          IsEnemy(i, j+1, Field[i][j]);
            o =  OuterCount(i,j);

            return true;
        }
        return false;
    }

    void TryAdd(std::deque<pair<int,int>>& q, int i, int j, char type)
    {
        unsigned int b = 0;
        unsigned int o = 0;
        if (IsSuitable(i, j, type, b, o)) {
            border += b;
            outer += o;
            q.emplace_back(i, j);
            visited.insert(make_pair(i, j));
        }
    }

public:
    Task120_Opt(const vector<string>& field)
        : Task120(field)
    {}


    Result RunBorderSearch(int i, int j)
    {
        const auto total = 2*(Width+Height);

        std::deque<pair<int,int>> q;

        q.emplace_back(i,j);

        border += IsEnemy(i-1, j, Field[i][j]) +
                  IsEnemy(i+1, j, Field[i][j]) +
                  IsEnemy(i, j-1, Field[i][j]) +
                  IsEnemy(i, j+1, Field[i][j]);

        outer +=  OuterCount(i,j);

        visited.insert(make_pair(i,j));

        auto type = Field[i][j];

        while (!q.empty()) {
            int i,j;
            tie<int,int>(i,j) = q.front();
            q.pop_front();

            TryAdd(q, i-1, j ,type);
            TryAdd(q, i+1, j, type);
            TryAdd(q, i, j-1, type);
            TryAdd(q, i, j+1, type);
        }

        if (type == 'R') {
            return {border, outer + border, (total - outer) + border};
        } else {
            return {border, (total - outer) + border, outer + border};
        }
    }

    Result solve()
    {
        auto result = RunBorderSearch(0,0);

        if (result.Border == 0) {
            // slow path

            int ei = 0,ej = 0;

            for (int i = 0; i < Field.size(); ++i) {
                auto it = Field[i].find(Field[0][0] == 'F' ? 'R' : 'F');
                if (it != std::string::npos) {
                    ei = i;
                    ej = (int)it;
                    break;
                }
            }

            border = 0;
            outer = 0;

            auto result1 = RunBorderSearch(ei, ej);

            return {
                result1.Border,
                result.Rperimeter + result1.Border,
                result1.Fperimeter
                };
        }

        return result;
    }
};

int main() {

    {
        auto result = Task120_Quadratic({"RR","RF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"RR","RF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"RRRRRRR","RRRRRRR","RRRRRRR","RRRFRRR","RRRRRRR","RRRRRRR","RRRRRRR"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"RRRRRRR","RRRRRRR","RRRRRRR","RRRFRRR","RRRRRRR","RRRRRRR","RRRRRRR"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"FFFFFFF","FFFFFFF","FFFFFFF","FFFRFFF","FFFFFFF","FFFFFFF","FFFFFFF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"FFFFFFF","FFFFFFF","FFFFFFF","FFFRFFF","FFFFFFF","FFFFFFF","FFFFFFF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"FFF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"FFF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"FFF","FRF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"FFF","FRF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"FFF","FRF","FRF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"FFF","FRF","FRF","FRF","FRF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }

    {
        auto result = Task120_Quadratic({"FFFFF","FFFFF","FFFFF","FFRFF","FFRFF","FFRFF","FFRFF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
        result = Task120_Opt({"FFFFF","FFFFF","FFFFF","FFRFF","FFRFF","FFRFF","FFRFF"}).solve();
        cout << result.Border << " " << result.Rperimeter << " " << result.Fperimeter << endl;
    }


    return 0;
}