#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vi;
typedef vector<ll> vl;
#define REP(i, n) for (int i = 0, i##_len = (n); i < i##_len; ++i)
#define FOR(i, a, b) for (int i = (a), i##_len = (b); i < i##_len; i++)
#define ALL(x) (x).begin(), (x).end()  // sortなどの引数を省略したい
#ifdef _DEBUG
#define PRE_COMMAND             \
    std::cin.rdbuf(in.rdbuf()); \
    std::cout << fixed << setprecision(15);
#else
#define PRE_COMMAND cout << fixed << setprecision(15);
#endif
const double EPS = 1e-10, PI = acos(-1.0);
template <class T> auto MAX(T& seq) {
    return *max_element(seq.begin(), seq.end());
}
template <class T> auto MIN(T& seq) {
    return *min_element(seq.begin(), seq.end());
}
template <class T> auto SUM(T& seq) {
    T temp{0};
    auto& temp2 = temp[0];
    return accumulate(seq.begin(), seq.end(), temp2);
}
template <class T> void SORT(T& seq) { sort(seq.begin(), seq.end()); }
template <class T, class S> void SORT(T& seq, S& sort_order) {
    sort(seq.begin(), seq.end(), sort_order);
}
template <class T> void SORTR(vector<T>& seq) {
    sort(seq.begin(), seq.end(), greater<T>());
}
template <class T, class S, class R> long long pow(T n_0, S k_0, R mod_0) {
    long long n = n_0 % mod_0, k = k_0, mod = mod_0, now = 1;
    while (k) {
        if (k & 1) now = (now * n) % mod;
        n = (n * n) % mod;
        n >>= 1;
    }
    return now;
}
template <typename T> istream& operator>>(istream& is, vector<T>& vec) {
    for (T& x : vec) is >> x;
    return is;
}

// 頂点の数、辺の情報、indexed
template <class num_type> struct max_flow {
    struct edge {
        int to, rev;
        num_type cap;
        edge(int to_temp, ll cap_temp, int rev_temp)
            : to(to_temp), cap(cap_temp), rev(rev_temp) {}
    };
    int n, m = 0;               // 頂点数と辺数
    num_type inf = pow(10, 9);  // 無限大
    vector<vector<edge>> g;     // 隣接グラフ
    vector<int> level, iter;  // 距離とどこまで検索終了しているか
    set<tuple<int, int>> edge_set;  // 辺の集合
    // コンストラクタ
    max_flow(int n_temp) : n(n_temp) { g.assign(n, {}); }
    max_flow(int n_temp, vector<tuple<int, int, num_type>> g_temp) : n(n_temp) {
        int from, to;
        num_type cap;
        g.assign(n, {});
        for (tuple<int, int, num_type> t_temp : g_temp) {
            tie(from, to, cap) = t_temp;
            add_edge(from, to, cap);
        }
    }
    // 辺の追加
    int add_edge(int from, int to, num_type cap) {
        g[from].push_back((edge){to, cap, (int)g[to].size()});
        g[to].push_back((edge){from, 0, (int)g[from].size() - 1});
        edge_set.insert(make_tuple(from, to));
        return ++m;
    }
    // sからtへの最大流を計算
    num_type flow(int s, int t) {
        int f = 0, f2;
        for (;;) {
            bfs(s);
            if (level[t] < 0) return f;
            iter.assign(n, 0);
            while ((f2 = dfs(s, t, inf)) > 0) { f += f2; }
        }
    }
    // 残余グラフでsから到達できる頂点の集合を返す
    // flmake_tuple(from, to)ow(s, *)を流してから実行する
    vector<int> min_cut(int s) {
        vector<int> ret(n, 0);
        for (int i = 0; i < n; i++) { ret[i] = (int)(level[i] > -1); }
        return ret;
    }
    // 辺の状態を返す
    vector<vector<tuple<int, num_type>>> get_edges() {
        vector<vector<tuple<int, num_type>>> ret(n);
        int from, to, rev;
        num_type f;
        for (int from = 0; from < n; from++) {
            for (edge temp : g[from]) {
                to = temp.to;
                if (edge_set.count(make_tuple(from, to))) {
                    ret[from].push_back(make_tuple(to, g[to][temp.rev].cap));
                }
            }
        }
        return ret;
    }
    // flow用のBFS関数
    void bfs(int s) {
        level.assign(n, -1);
        deque<int> que;
        level[s] = 0;
        que.push_back(s);
        while (!que.empty()) {
            int v = que.front();
            que.pop_front();
            for (int i = 0; i < (int)g[v].size(); i++) {
                edge& e = g[v][i];
                if (e.cap > 0 && level[e.to] < 0) {
                    level[e.to] = level[v] + 1;
                    que.push_back(e.to);
                }
            }
        }
    }
    // flow用のDFS関数
    int dfs(int v, int t, num_type f) {
        if (v == t) return f;
        for (int& i = iter[v]; i < (int)g[v].size(); i++) {
            edge& e = g[v][i];
            if (e.cap > 0 && level[v] < level[e.to]) {
                int f2 = dfs(e.to, t, min(f, e.cap));
                if (f2 > 0) {
                    e.cap -= f2;
                    g[e.to][e.rev].cap += f2;
                    return f2;
                }
            }
        }
        return 0;
    }
};

// 最大マッチングを行う、最大フローのライブラリも一緒に持ってくる
// 入力：xのサイズ、yのサイズ、xy間の辺、indexed
vector<tuple<int, int>> bimatch(int x_size, int y_size,
                                vector<tuple<int, int>> xy) {
    int m = (int)xy.size(), n = x_size + y_size + 2;
    int m2 = x_size + y_size + m;
    vector<tuple<int, int, int>> edge(m2);
    for (int i = 0; i < m; i++) {
        int x, y;
        tie(x, y) = xy[i];
        edge[i] = make_tuple(x + 1, y + x_size + 1, 1);
    }
    for (int i = 0; i < x_size; i++) { edge[m + i] = make_tuple(0, i + 1, 1); }
    for (int i = 0; i < y_size; i++) {
        edge[m + x_size + i] = make_tuple(i + x_size + 1, n - 1, 1);
    }
    max_flow<int> flow(n, edge);
    flow.flow(0, n - 1);
    vector<vector<tuple<int, int>>> g = flow.get_edges();
    vector<tuple<int, int>> ret;
    for (int x = 1; x < (int)g.size(); x++) {
        int y, z, v;
        for (tuple<int, int> t : g[x]) {
            tie(y, v) = t;
            if (y == n - 1 || v == 0) continue;
            ret.push_back(make_tuple(x - 1, y - x_size - 1));
        }
    }
    return ret;
}

int main() {
    PRE_COMMAND
    int h, w, x, y;
    cin >> h >> w;
    vector<vector<char>> v_s(h, vector<char>(w));
    cin >> v_s;
    vector<tuple<int, int>> v_t;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            if ((i + j) % 2 || v_s[i][j] == '#') { continue; }
            x = i * w + j;
            if (i != 0 && v_s[i - 1][j] == '.')
                v_t.push_back(make_tuple(x, x - w));
            if (i != h - 1 && v_s[i + 1][j] == '.')
                v_t.push_back(make_tuple(x, x + w));
            if (j != 0 && v_s[i][j - 1] == '.')
                v_t.push_back(make_tuple(x, x - 1));
            if (j != w - 1 && v_s[i][j + 1] == '.')
                v_t.push_back(make_tuple(x, x + 1));
        }
    }
    for (int i = 0; i < (int)v_t.size(); i++) {
        tie(x, y) = v_t[i];
        v_t[i] = make_tuple(x / 2, y / 2);
    }
    map<int, tuple<int, int>> x_dict, y_dict;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) {
            if ((i + j) % 2 == 0) {
                x_dict[(i * w + j) / 2] = make_tuple(i, j);
            } else {
                y_dict[(i * w + j) / 2] = make_tuple(i, j);
            }
        }
    }
    int y_size = h * w / 2;
    int x_size = h * w - y_size;
    vector<tuple<int, int>> b = bimatch(x_size, y_size, v_t);
    for (auto temp : b) {
        tie(x, y) = temp;
        int x0, x1, y0, y1;
        tie(x0, x1) = x_dict[x], tie(y0, y1) = y_dict[y];
        if (x0 == y0) {
            if (x1 > y1) swap(x1, y1);
            v_s[x0][x1] = '>';
            v_s[y0][y1] = '<';
        }
        if (x1 == y1) {
            if (x0 > y0) swap(x0, y0);
            v_s[x0][x1] = 'v';
            v_s[y0][y1] = '^';
        }
    }
    cout << b.size() << endl;
    for (int i = 0; i < h; i++) {
        for (int j = 0; j < w; j++) { cout << v_s[i][j]; }
        cout << endl;
    }
}

Measured memory_bytes: 8156