Buggy python submission (verdict: Time Limit Exceeded):
class Edge():
    def __init__(self, to, cap, rev=None):
        self.to = to
        self.cap = cap
        self.rev = rev

from collections import deque

class Dinic():
    def __init__(self, n):
        self.n = n
        self.inf = 10**18
        self.graph = [[] for _ in range(self.n)]

    def add(self, fr, to, cap):
        edge = Edge(to, cap)
        rev = Edge(fr, 0)
        edge.rev, rev.rev = rev, edge
        self.graph[fr].append(edge)
        self.graph[to].append(rev)

    def max_flow(self, source, sink):
        flow = 0
        while True:
            queue = deque([source])
            level = [None for _ in range(self.n)]
            level[source] = 0
            while queue:
                node = queue.popleft()
                for edge in self.graph[node]:
                    to = edge.to
                    if edge.cap and level[to] is None:
                        level[to] = level[node] + 1
                        queue.append(to)
            if level[sink] is None: break
            stack = [source]
            par = [None for _ in range(self.n)]
            par[source] = source
            cap = [self.inf for _ in range(self.n)]
            while stack:
                node = stack.pop()
                if node == sink:
                    break
                for edge in self.graph[node]:
                    to = edge.to
                    if edge.cap and level[node] < level[to]:
                        par[to] = node
                        cap[to] = min(edge.cap, cap[node])
                        stack.append(to)
            node = sink
            prev = par[sink]
            delta = cap[sink]
            while node != source:
                for edge in self.graph[prev]:
                    if edge.to == node:
                        edge.cap -= delta
                        edge.rev.cap += delta
                node = prev
                prev = par[node]
            flow += delta
        return flow

import sys
input = sys.stdin.readline

N, M = map(int, input().split())
S = [input() for _ in range(N)]

d = Dinic(N * M + 2)

dx = [1, 0, -1, 0]
dy = [0, 1, 0, -1]

res = [['.' for j in range(M)] for i in range(N)]

for i in range(N):
    for j in range(M):
        if S[i][j] == '#':
            res[i][j] = '#'
            continue
        if (i + j) % 2 == 0:
            d.add(N * M, i * M + j, 1)
            for k in range(4):
                ni = i + dx[k]
                nj = j + dy[k]
                if 0 <= ni < N and 0 <= nj < M and S[ni][nj] == '.':
                    d.add(i * M + j, ni * M + nj, 1)
        else:
            d.add(i * M + j, N * M + 1, 1)

print(d.max_flow(M * N, M * N + 1))

for i in range(N):
    for j in range(M):
        if S[i][j] == '#' or (i + j) % 2:
            continue
        for edge in d.graph[i * M + j]:
            if edge.to == N * M or edge.cap == 1:
                continue
            ti = edge.to // M
            tj = edge.to % M
            if i + 1 == ti:
                res[i][j] = 'v'
                res[ti][tj] = '^'
            elif j + 1 == tj:
                res[i][j] = '>'
                res[ti][tj] = '<'
            elif i == ti + 1:
                res[i][j] = '^'
                res[ti][tj] = 'v'
            else:
                res[i][j] = '<'
                res[ti][tj] = '>'

for s in res:
    print(''.join(s))

Fixed submission:
from collections import deque

class MaxFlow():
    def __init__(self, n):
        self.n = n
        self.graph = [[] for _ in range(n)]
        self.pos = []

    def add_edge(self, fr, to, cap):
        assert 0 <= fr < self.n
        assert 0 <= to < self.n
        assert 0 <= cap
        m = len(self.pos)
        self.pos.append((fr, len(self.graph[fr])))
        self.graph[fr].append([to, len(self.graph[to]), cap])
        self.graph[to].append([fr, len(self.graph[fr]) - 1, 0])
        return m

    def get_edge(self, idx):
        m = len(self.pos)
        assert 0 <= idx < m
        to, rev, cap = self.graph[self.pos[idx][0]][self.pos[idx][1]]
        rev_to, rev_rev, rev_cap = self.graph[to][rev]
        return rev_to, to, cap + rev_cap, rev_cap

    def edges(self):
        m = len(self.pos)
        res = []
        for i in range(m):
            res.append(self.get_edge(i))
        return res

    def change_edge(self, idx, new_cap, new_flow):
        m = len(self.pos)
        assert 0 <= idx < m
        assert 0 <= new_flow <= new_cap
        to, rev, cap = self.graph[self.pos[idx][0]][self.pos[idx][1]]
        self.graph[self.pos[idx][0]][self.pos[idx][1]][2] = new_cap - new_flow
        self.graph[to][rev][2] = new_flow

    def dfs(self, s, v, up):
        if v == s: return up
        res = 0
        lv = self.level[v]
        for i in range(self.iter[v], len(self.graph[v])):
            to, rev, cap = self.graph[v][i]
            if lv <= self.level[to] or self.graph[to][rev][2] == 0: continue
            d = self.dfs(s, to, min(up - res, self.graph[to][rev][2]))
            if d <= 0: continue
            self.graph[v][i][2] += d
            self.graph[to][rev][2] -= d
            res += d
            if res == up: break
        return res

    def max_flow(self, s, t):
        return self.max_flow_with_limit(s, t, 2**63 - 1)

    def max_flow_with_limit(self, s, t, limit):
        assert 0 <= s < self.n
        assert 0 <= t < self.n
        flow = 0
        while flow < limit:
            self.level = [-1] * self.n
            self.level[s] = 0
            queue = deque()
            queue.append(s)
            while queue:
                v = queue.popleft()
                for to, rev, cap in self.graph[v]:
                    if cap == 0 or self.level[to] >= 0: continue
                    self.level[to] = self.level[v] + 1
                    if to == t: break
                    queue.append(to)
            if self.level[t] == -1: break
            self.iter = [0] * self.n
            while flow < limit:
                f = self.dfs(s, t, limit - flow)
                if not f: break
                flow += f
        return flow

    def min_cut(self, s):
        visited = [0] * self.n
        queue = deque()
        queue.append(s)
        while queue:
            p = queue.popleft()
            visited[p] = True
            for to, rev, cap in self.graph[p]:
                if cap and not visited[to]:
                    visited[to] = True
                    queue.append(to)
        return visited

import sys
input = sys.stdin.readline

N, M = map(int, input().split())
S = [input() for _ in range(N)]

mf = MaxFlow(N * M + 2)

dx = [1, 0, -1, 0]
dy = [0, 1, 0, -1]

res = [['.' for j in range(M)] for i in range(N)]

for i in range(N):
    for j in range(M):
        if S[i][j] == '#':
            res[i][j] = '#'
            continue
        if (i + j) % 2 == 0:
            mf.add_edge(N * M, i * M + j, 1)
            for k in range(4):
                ni = i + dx[k]
                nj = j + dy[k]
                if 0 <= ni < N and 0 <= nj < M and S[ni][nj] == '.':
                    mf.add_edge(i * M + j, ni * M + nj, 1)
        else:
            mf.add_edge(i * M + j, N * M + 1, 1)

print(mf.max_flow(M * N, M * N + 1))

for fr, to, cap, flow in mf.edges():
    if fr == N * M or to == N * M + 1 or flow == 0:
        continue
    si = fr // M
    sj = fr % M
    ti = to // M
    tj = to % M
    if si + 1 == ti:
        res[si][sj] = 'v'
        res[ti][tj] = '^'
    elif sj + 1 == tj:
        res[si][sj] = '>'
        res[ti][tj] = '<'
    elif si == ti + 1:
        res[si][sj] = '^'
        res[ti][tj] = 'v'
    else:
        res[si][sj] = '<'
        res[ti][tj] = '>'

for s in res:
    print(''.join(s))