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

// 型定義
typedef long long ll;
typedef pair<ll, ll> P;

// forループ
#define REP(i,n) for(ll i=0; i<(ll)(n); ++i)

// 定数宣言
const int INF = 1e9;
const int MOD = 1e9+7;
const ll LINF = 1e18;

// グラフ表現
using Graph = vector<vector<int>>;

// グラフの辺表現
using Edge = map<pair<int,int>,int>;

// n次元配列の初期化。第２引数の型のサイズごとに初期化していく。
template<typename A, size_t N, typename T>
void Fill(A (&array)[N], const T &val){
    std::fill( (T*)array, (T*)(array+N), val );
}

// 最大公約数
ll gcd(ll a,ll b){
   if (a%b == 0) return(b);
   else return(gcd(b, a%b));
}

// 最小公倍数
ll lcm(ll a, ll b){
    return a/gcd(a, b) * b;
}

int main()
{
    cout << fixed << setprecision(15);
    ll N, M;
    cin >> N;
    cin >> M;

    vector<string> S(N);

    REP(i, N){
        cin >> S[i];
    }

    mf_graph<ll> G(N*M+2);
    
    ll s = N*M;
    ll t = N*M+1;

    vector<vector<char>> ans(N, vector<char>(M));

    // まずはグラフ作成
    REP(i, N){
        REP(j, M){
            // 結果記録用の配列を一緒に作っておく
            ans[i][j] = S[i][j];

            // もしも#なら辺を作らない
            if(S[i][j] == '#') continue;
            
            ll v = i*M + j;

            // 奇数ならsと偶数ならtと結んでおく
            if((i+j)%2 == 1) G.add_edge(s, v, 1);
            else G.add_edge(v, t, 1);

            // 偶数側なら結ばれる側なので処理を行わない
            if((i+j)%2 == 0) continue;

            // 上と結ぶ場合
            if(i-1 >= 0){
                if(S[i-1][j] == '.') G.add_edge(v, v-M, 1);
            } 
            // 左と結ぶ場合
            if(j-1 >= 0){
                if(S[i][j-1] == '.') G.add_edge(v, v-1, 1);
            }
            // 下と結ぶ場合
            if(i+1 <= N-1){
                if(S[i+1][j] == '.') G.add_edge(v, v+M, 1);
            }
            // 右と結ぶ場合
            if(j+1 <= M-1){
                if(S[i][j+1] == '.') G.add_edge(v, v+1, 1);
            }
        }
    }

    // auto pre_edges = G.edges();
    // for(auto e: pre_edges){

    //     if(e.from == s || e.to == t) continue;
    //     // 辺の情報をグリッド位置へ戻す
    //     ll i0 = e.from / M;
    //     ll j0 = e.from % M;
    //     ll i1 = e.to / M;
    //     ll j1 = e.to % M;
    //     cout << "(" << i0 <<  "," << j0 << ")--(" << i1 <<  "," << j1 << ")" << endl;
    // }
    
    cout << G.flow(s, t) << endl;
    // G.flow(s, t);

    auto edges = G.edges();

    for(auto e: edges){

        // ソースやシンクと紐付いている辺は関係ないのでスキップ
        if(e.from == s || e.to == t || e.flow == 0) continue;

        // 辺の情報をグリッド位置へ戻す
        ll i0 = e.from / M;
        ll j0 = e.from % M;
        ll i1 = e.to / M;
        ll j1 = e.to % M;

        // cout << "(" << i0 <<  "," << j0 << ")--(" << i1 <<  "," << j1 << ")" << endl;
        // 偶数側なら処理を行わない
        if((i0+j0)%2 == 0) continue;
        
        // 下にくっつくとき
        if(i0 + 1 == i1){
            ans[i0][j0] = 'v';
            ans[i1][j1] = '^';
        }

        // 上にくっつくとき
        if(i0 - 1 == i1){
            ans[i0][j0] = '^';
            ans[i1][j1] = 'v';
        }

        // 左にくっつくとき
        if(j0 - 1 == j1){
            ans[i0][j0] = '<';
            ans[i1][j1] = '>';
        }

        // 右にくっつくとき
        if(j0 + 1 == j1){
            ans[i0][j0] = '>';
            ans[i1][j1] = '<';
        }
    }

    REP(i, N){
        REP(j, M){
            cout << ans[i][j];
        }
        cout << endl;
    }
    return 0;
}

Measured memory_bytes: 6400