#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Stałe dla maksymalnych rozmiarów, zgodnie z treścią zadania (1 mln)
const int MAXN = 1000005;
const int MAXM = 1000005;
// Struktura do przechowywania oryginalnych krawędzi, aby ustalić kierunek
struct Edge {
int u, v;
} edges[MAXM];
// Lista sąsiedztwa: pair<sąsiad, numer_krawędzi>
vector<pair<int, int>> adj[MAXN];
// Tablice pomocnicze do DFS i znajdowania mostów
int tin[MAXN]; // Time In - czas wejścia do wierzchołka
int low[MAXN]; // Low Link - najniższy tin osiągalny przez krawędź powrotną
int timer; // Licznik czasu
bool visited[MAXN]; // Czy odwiedzono
// Tablica na wynikowe znaki kierunków (>, <)
char result_dirs[MAXM];
int bridges_count = 0; // Licznik mostów
// Główna funkcja DFS
void dfs(int u, int p_edge_id) {
visited[u] = true;
tin[u] = low[u] = ++timer;
for (auto& edge : adj[u]) {
int v = edge.first;
int id = edge.second;
// Nie wracamy tą samą krawędzią, którą przyszliśmy (ale równoległą możemy!)
if (id == p_edge_id) continue;
if (visited[v]) {
// Krawędź powrotna lub już odwiedzona inna krawędź równoległa
low[u] = min(low[u], tin[v]);
// Jeśli kierunek tej krawędzi nie został jeszcze ustalony, ustalamy go teraz.
// Dla krawędzi powrotnej orientujemy u -> v (domykamy cykl)
if (result_dirs[id] == 0) {
if (edges[id].u == u) result_dirs[id] = '>'; // u to pierwszy wierzchołek z wejścia
else result_dirs[id] = '<'; // u to drugi wierzchołek z wejścia
}
} else {
// Krawędź drzewowa
// Orientujemy zgodnie z kierunkiem DFS: u -> v
if (edges[id].u == u) result_dirs[id] = '>';
else result_dirs[id] = '<';
dfs(v, id);
low[u] = min(low[u], low[v]);
// Sprawdzenie warunku na most
if (low[v] > tin[u]) {
bridges_count++;
}
}
}
}
int main() {
// Optymalizacja I/O
ios_base::sync_with_stdio(false);
cin.tie(NULL);
int n, m;
if (!(cin >> n >> m)) return 0;
for (int i = 1; i <= m; ++i) {
cin >> edges[i].u >> edges[i].v;
// Dodajemy krawędź do listy sąsiedztwa obu wierzchołków
adj[edges[i].u].push_back({edges[i].v, i});
adj[edges[i].v].push_back({edges[i].u, i});
result_dirs[i] = 0; // 0 oznacza, że kierunek nie został jeszcze nadany
}
int connected_components = 0;
// Uruchamiamy DFS dla każdej spójnej składowej grafu
for (int i = 1; i <= n; ++i) {
if (!visited[i]) {
connected_components++;
dfs(i, -1);
}
}
// Minimalna liczba osiedli = liczba spójnych składowych + liczba mostów
cout << connected_components + bridges_count << "\n";
// Wypisujemy ciąg znaków reprezentujący kierunki
for (int i = 1; i <= m; ++i) {
// Zabezpieczenie: jeśli z jakiegoś powodu (np. graf niespójny i pętle)
// kierunek nie został ustawiony, domyślnie dajemy '>'
if (result_dirs[i] == 0) cout << '>';
else cout << result_dirs[i];
}
cout << "\n";
return 0;
}