package main

import (
	"bufio"
	"fmt"
	"math"
	"os"
	"strconv"
	"strings"
)

func configure(scanner *bufio.Scanner) {
	scanner.Split(bufio.ScanWords)
	scanner.Buffer(make([]byte, 1000005), 1000005)
}
func getNextString(scanner *bufio.Scanner) string {
	scanned := scanner.Scan()
	if !scanned {
		panic("scan failed")
	}
	return scanner.Text()
}
func getNextInt(scanner *bufio.Scanner) int {
	i, _ := strconv.Atoi(getNextString(scanner))
	return i
}
func getNextInt64(scanner *bufio.Scanner) int64 {
	i, _ := strconv.ParseInt(getNextString(scanner), 10, 64)
	return i
}
func getNextFloat64(scanner *bufio.Scanner) float64 {
	i, _ := strconv.ParseFloat(getNextString(scanner), 64)
	return i
}
func main() {
	fp := os.Stdin
	wfp := os.Stdout
	extra := 0
	if os.Getenv("I") == "IronMan" {
		fp, _ = os.Open(os.Getenv("END_GAME"))
		extra = 100
	}
	scanner := bufio.NewScanner(fp)
	configure(scanner)
	writer := bufio.NewWriter(wfp)
	defer func() {
		r := recover()
		if r != nil {
			fmt.Fprintln(writer, r)
		}
		writer.Flush()
	}()
	solve(scanner, writer)
	for i := 0; i < extra; i++ {
		fmt.Fprintln(writer, "-----------------------------------")
		solve(scanner, writer)
	}
}
func solve(scanner *bufio.Scanner, writer *bufio.Writer) {
	n := getNextInt(scanner)
	m := getNextInt(scanner)
	ss := make([][]string, n)
	for i := 0; i < n; i++ {
		ss[i] = strings.Split(getNextString(scanner), "")
	}
	g := NewMfGraph(n*m + 2)
	s := n * m
	t := s + 1
	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if ss[i][j] == "#" {
				continue
			}
			v := i*m + j
			if (i+j)&1 == 0 {
				g.AddEdge(s, v, 1)
				continue
			}
			g.AddEdge(v, t, 1)
		}
	}

	for i := 0; i < n; i++ {
		for j := 0; j < m; j++ {
			if (i+j)&1 == 1 || ss[i][j] == "#" {
				continue
			}
			v0 := i*m + j
			if i > 0 && ss[i-1][j] == "." {
				v1 := (i-1)*m + j
				g.AddEdge(v0, v1, 1)
			}
			if j > 0 && ss[i][j-1] == "." {
				v1 := i*m + j - 1
				g.AddEdge(v0, v1, 1)
			}
			if i+1 < n && ss[i+1][j] == "." {
				v1 := (i+1)*m + j
				g.AddEdge(v0, v1, 1)
			}
			if j+1 < m && ss[i][j+1] == "." {
				v1 := i*m + j + 1
				g.AddEdge(v0, v1, 1)
			}
		}
	}
	fmt.Fprintln(writer, g.Flow(s, t))
	edges := g.Edges()
	for i := 0; i < len(edges); i++ {
		e := edges[i]
		if e.From == s || e.To == t || e.Flow == 0 {
			continue
		}
		i0 := e.From / m
		j0 := e.From % m
		i1 := e.To / m
		j1 := e.To % m
		switch {
		case i0 == i1+1:
			ss[i1][j1] = "v"
			ss[i0][j0] = "^"
		case j0 == j1+1:
			ss[i1][j1] = ">"
			ss[i0][j0] = "<"
		case i0 == i1-1:
			ss[i0][j0] = "v"
			ss[i1][j1] = "^"
		default:
			ss[i0][j0] = ">"
			ss[i1][j1] = "<"
		}
	}

	for i := 0; i < n; i++ {
		fmt.Fprintln(writer, strings.Join(ss[i], ""))
	}
}

type edge struct {
	To, Rev int
	Cap     int64
}

// Edge for MfGraph
type Edge struct {
	From, To  int
	Cap, Flow int
}

// MfGraph solves maximum flow problem.
type MfGraph struct {
	g *MfGraph64
}

// Edge64 for MfGraph64
type Edge64 struct {
	From, To  int
	Cap, Flow int64
}

// MfGraph64 solves maximum flow problem.
type MfGraph64 struct {
	n   int
	pos [][2]int
	g   [][]*edge
}

// NewMfGraph creates a graph of n vertices and 0 edges.
func NewMfGraph(n int) *MfGraph {
	return &MfGraph{
		g: NewMfGraph64(n),
	}
}

// AddEdge adds an edge oriented from the vertex from to the vertex to with the capacity cap and the flow amount 0.
// It returns an integer k such that this is the k-th edge that is added.
func (m *MfGraph) AddEdge(from, to int, cap int) int {
	return m.g.AddEdge(from, to, int64(cap))
}

// GetEdge returns the current internal state of the edges.
// The edges are ordered in the same order as added by add_edge.
func (m *MfGraph) GetEdge(i int) *Edge {
	e := m.g.GetEdge(i)
	return &Edge{
		From: e.From,
		To:   e.To,
		Cap:  int(e.Cap),
		Flow: int(e.Flow),
	}
}

// Edges returns the current internal state of the edges.
// The edges are ordered in the same order as added by add_edge.
func (m *MfGraph) Edges() []*Edge {
	edges := m.g.Edges()
	result := make([]*Edge, len(edges))
	for i := 0; i < len(edges); i++ {
		e := edges[i]
		result[i] = &Edge{
			From: e.From,
			To:   e.To,
			Cap:  int(e.Cap),
			Flow: int(e.Flow),
		}
	}
	return result
}

// ChangeEdge changes the capacity and the flow amount of the i-th edge to new_cap and new_flow, respectively.
// It doesn't change the capacity or the flow amount of other edges.
func (m *MfGraph) ChangeEdge(i int, newCap, newFlow int) {
	m.g.ChangeEdge(i, int64(newCap), int64(newFlow))
}

// Flow augments the flow from s to t as much as possible. It returns the amount of the flow augmented.
// You may call it multiple times.
func (m *MfGraph) Flow(s, t int) int {
	return m.FlowWithLimit(s, t, math.MaxInt32)
}

// FlowWithLimit augments the flow from s to t as much as possible. It returns the amount of the flow augmented.
// You may call it multiple times.
func (m *MfGraph) FlowWithLimit(s, t int, flowLimit int) int {
	return int(m.g.FlowWithLimit(s, t, int64(flowLimit)))
}

// MinCut returns a slice of length n, such that the i-th element is true if and only if there is a directed path from s to i in the residual network.
// The returned slice corresponds to a s-t minimum cut after calling Flow(s, t) exactly once without flow_limit.
func (m *MfGraph) MinCut(s int) []bool {
	return m.g.MinCut(s)
}

// NewMfGraph64 creates a graph of n vertices and 0 edges.
func NewMfGraph64(n int) *MfGraph64 {
	return &MfGraph64{
		n: n,
		g: make([][]*edge, n),
	}
}

// AddEdge adds an edge oriented from the vertex from to the vertex to with the capacity cap and the flow amount 0.
// It returns an integer k such that this is the k-th edge that is added.
func (m *MfGraph64) AddEdge(from, to int, cap int64) int {
	ps := len(m.pos)
	m.pos = append(m.pos, [2]int{from, len(m.g[from])})
	m.g[from] = append(m.g[from], &edge{
		To:  to,
		Rev: len(m.g[to]),
		Cap: cap,
	})
	m.g[to] = append(m.g[to], &edge{
		To:  from,
		Rev: len(m.g[from]) - 1,
		Cap: 0,
	})
	return ps
}

// GetEdge returns the current internal state of the edges.
// The edges are ordered in the same order as added by add_edge.
func (m *MfGraph64) GetEdge(i int) *Edge64 {
	e := m.g[m.pos[i][0]][m.pos[i][1]]
	re := m.g[e.To][e.Rev]
	return &Edge64{
		From: m.pos[i][0],
		To:   e.To,
		Cap:  e.Cap + re.Cap,
		Flow: re.Cap,
	}
}

// Edges returns the current internal state of the edges.
// The edges are ordered in the same order as added by add_edge.
func (m *MfGraph64) Edges() []*Edge64 {
	ps := len(m.pos)
	result := make([]*Edge64, ps)
	for i := 0; i < ps; i++ {
		result[i] = m.GetEdge(i)
	}
	return result
}

// ChangeEdge changes the capacity and the flow amount of the i-th edge to new_cap and new_flow, respectively.
// It doesn't change the capacity or the flow amount of other edges.
func (m *MfGraph64) ChangeEdge(i int, newCap, newFlow int64) {
	e := m.g[m.pos[i][0]][m.pos[i][1]]
	re := m.g[e.To][e.Rev]
	e.Cap = newCap - newFlow
	re.Cap = newFlow
}

// Flow augments the flow from s to t as much as possible. It returns the amount of the flow augmented.
// You may call it multiple times.
func (m *MfGraph64) Flow(s, t int) int64 {
	return m.FlowWithLimit(s, t, math.MaxInt64)
}

// FlowWithLimit augments the flow from s to t as much as possible. It returns the amount of the flow augmented.
// You may call it multiple times.
func (m *MfGraph64) FlowWithLimit(s, t int, flowLimit int64) int64 {
	n := m.n
	level := make([]int, n)
	iter := make([]int, n)
	que := make([]int, 0)
	var bfs func()
	bfs = func() {
		for i := 0; i < n; i++ {
			level[i] = -1
		}
		level[s] = 0
		que = que[:0]
		que = append(que, s)
		for len(que) > 0 {
			v := que[0]
			que = que[1:]
			for _, e := range m.g[v] {
				if e.Cap == 0 || level[e.To] >= 0 {
					continue
				}
				level[e.To] = level[v] + 1
				if e.To == t {
					return
				}
				que = append(que, e.To)
			}
		}
	}
	var dfs func(int, int64) int64
	dfs = func(v int, up int64) int64 {
		if v == s {
			return up
		}
		var res int64
		res = 0
		lv := level[v]
		for i := &iter[v]; *i < len(m.g[v]); *i++ {
			e := m.g[v][*i]
			if lv <= level[e.To] || m.g[e.To][e.Rev].Cap == 0 {
				continue
			}
			u := up - res
			if up-res > m.g[e.To][e.Rev].Cap {
				u = m.g[e.To][e.Rev].Cap
			}
			d := dfs(e.To, u)
			if d <= 0 {
				continue
			}
			m.g[v][*i].Cap += d
			m.g[e.To][e.Rev].Cap -= d
			res += d
			if res == up {
				break
			}
		}
		return res
	}
	var flow int64
	for flow < flowLimit {
		bfs()
		if level[t] == -1 {
			break
		}
		for i := 0; i < n; i++ {
			iter[i] = 0
		}
		for flow < flowLimit {
			f := dfs(t, flowLimit-flow)
			if f == 0 {
				break
			}
			flow += f
		}
	}
	return flow
}

// MinCut returns a slice of length n, such that the i-th element is true if and only if there is a directed path from s to i in the residual network.
// The returned slice corresponds to a s-t minimum cut after calling Flow(s, t) exactly once without flow_limit.
func (m *MfGraph64) MinCut(s int) []bool {
	visited := make([]bool, m.n)
	que := make([]int, 0)
	que = append(que, s)
	for len(que) > 0 {
		p := que[0]
		que = que[1:]
		visited[p] = true
		for _, e := range m.g[p] {
			if e.Cap != 0 && !visited[e.To] {
				visited[e.To] = true
				que = append(que, e.To)
			}
		}
	}
	return visited
}


Measured memory_bytes: 8916