#include <iostream>
#include <vector>
#include <queue>
using namespace std;
const long long INF = (long long)1e18 + 100;

struct Edge
{
	int to;
	long long capacity;
};

struct Dinic
{
	struct Edge_Res
	{
		int to;
		long long capacity;
		int reverse_idx;
		bool is_backword;
	};

	int V;
	vector<vector<Edge_Res>> resnet;
	int s, t;
	long long max_flow;
	vector<int> level;
	vector<int> iter;

	Dinic(const vector<vector<Edge>> &g) : V(g.size()), resnet(V) {
		for (int i = 0; i < V; i++) {
			for (const Edge &e : g[i]) {
				resnet[i].push_back(Edge_Res{e.to, e.capacity, (int)resnet[e.to].size(), false});
				resnet[e.to].push_back(Edge_Res{i, 0, (int)resnet[i].size()-1, true});
			}
		}
	}

	Dinic(int V) : V(V), resnet(V) {}

	void add_edge(int from, int to, long long capacity) {
		resnet[from].push_back(Edge_Res{to, capacity, (int)resnet[to].size(), false});
		resnet[to].push_back(Edge_Res{from, 0, (int)resnet[from].size()-1, true});
	}

	bool is_s(int v) {
		return level[v] >= 0;
	}

	void calc(int s, int t) {
		this->s = s;
		this->t = t;
		max_flow = 0;
		while (true) {
			bfs(s);
			if (level[t] == -1) break;
			iter.assign(V, 0);
			while (true) {
				long long f = dfs(s, INF);
				if (f > 0) max_flow += f;
				else break;
			}
		}
		iter.clear();
		iter.shrink_to_fit();
	}

	void bfs(int s) {
		level.assign(V, -1);
		queue<int> q;
		q.push(s);
		level[s] = 0;
		while (!q.empty()) {
			int v = q.front(); q.pop();
			for (Edge_Res &e : resnet[v]) {
				if (e.capacity <= 0) continue;
				if (level[e.to] != -1) continue;
				level[e.to] = level[v] + 1;
				q.push(e.to);
			}
		}
	}

	long long dfs(int v, long long flow) {
		if (v == t) return flow;
		for (int &i = iter[v]; i < resnet[v].size(); i++) {
			Edge_Res &e = resnet[v][i];
			if (e.capacity <= 0) continue;
			if (level[e.to] <= level[v]) continue;
			long long f = dfs(e.to, min(flow, e.capacity));
			if (f == 0) continue;
			e.capacity -= f;
			resnet[e.to][e.reverse_idx].capacity += f;
			return f;
		}
		return 0;
	}
};

struct MaximumBipartiteMatching
{
	int A, B;
	Dinic mf;
	int s, t;
	int num_of_matching;
	vector<pair<int, int>> matching;

	MaximumBipartiteMatching(int A, int B) : A(A), B(B), mf(A+B+2), s(A+B), t(A+B+1) {
		for (int i = 0; i < A; i++) {
			mf.add_edge(s, i, 1);
		}
		for (int i = 0; i < B; i++) {
			mf.add_edge(A+i, t, 1);
		}
	}

	void add_edge(int a, int b) {
		mf.add_edge(a, A+b, 1);
	}

	void calc() {
		mf.calc(s, t);
		num_of_matching = mf.max_flow;
		matching.reserve(num_of_matching);
		for (int i = 0; i < A; i++) {
			for (auto &e : mf.resnet[i]) {
				if (!e.is_backword && e.capacity == 0) {
					matching.emplace_back(i, e.to - A);
				}
			}
		}
	}
};

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

int main() {
	int n, m;
	cin >> n >> m;
	vector<string> s(n);
	for (int i = 0; i < n; i++) {
		cin >> s[i];
	}
	auto idx = [&](int i, int j) -> int {
		return i * m + j;
	};
	auto idx_inv = [&](int idx) -> pair<int, int> {
		return {idx / m, idx % m};
	};
	MaximumBipartiteMatching bm(n*m, n*m);
	for (int i = 0; i < n; i++) {
		for (int j = i%2==0 ? 0 : 1; j < m; j += 2) {
			if (s[i][j] == '#') continue;
			for (int k = 0; k < 4; k++) {
				int ni = i + dx[k], nj = j + dy[k];
				if (ni < 0 || ni >= n || nj < 0 || nj >= m) continue;
				if (s[ni][nj] == '#') continue;
				bm.add_edge(idx(i,j), idx(ni,nj));
			}
		}
	}
	bm.calc();
	cout << bm.matching.size() << endl;
	for (auto e : bm.matching) {
		auto p1 = idx_inv(e.first), p2 = idx_inv(e.second);
		if (e.second == e.first - 1) {
			s[p1.first][p1.second] = '<';
			s[p2.first][p2.second] = '>';
		}
		if (e.second == e.first + 1) {
			s[p1.first][p1.second] = '>';
			s[p2.first][p2.second] = '<';
		}
		if (e.second == e.first - m) {
			s[p1.first][p1.second] = '^';
			s[p2.first][p2.second] = 'v';
		}
		if (e.second == e.first + m) {
			s[p1.first][p1.second] = 'v';
			s[p2.first][p2.second] = '^';
		}
	}
	for (int i = 0; i < n; i++) {
		cout << s[i] << endl;
	}
	return 0;
}

Measured memory_bytes: 6448