#include <bits/stdc++.h>
using namespace std;
inline void proc_status() {
  ifstream t("/proc/self/status");
  cerr << string(istreambuf_iterator<char>(t), istreambuf_iterator<char>())
       << endl;
}
namespace fastIO {
const unsigned int BUFFER_SIZE = (unsigned int)1 << 15;
char buffer[BUFFER_SIZE], *cur, *ed;
inline char getc() {
  return *(cur == ed    ? ed = buffer + fread(buffer, 1, BUFFER_SIZE, stdin),
           cur = buffer : cur)++;
}
}  // namespace fastIO
using fastIO::getc;
template <typename T>
inline T read() {
  register T sum(0), fg(1);
  register char ch(getc());
  for (; !isdigit(ch); ch = getc())
    if (ch == '-') fg = -1;
  for (; isdigit(ch); ch = getc()) sum = sum * 10 - '0' + ch;
  return sum * fg;
}
inline char *read_str(char *s) {
  register char ch(getc());
  while (!isgraph(ch)) ch = getc();
  for (; isgraph(ch); ch = getc()) *s++ = ch;
  *s = '\0';
  return s;
}
template <typename T>
inline T sqr(const T &val) {
  return val * val;
}
template <typename T>
inline int chkmin(T &a, const T &b) {
  return b < a ? a = b, 1 : 0;
}
template <typename T>
inline int chkmax(T &a, const T &b) {
  return b > a ? a = b, 1 : 0;
}
const int INF = (int)1e9;
const int MAXN = (int)2e5, MAXM = 6;
namespace FLOW {
const int MAXV = MAXM * MAXM + MAXM, MAXE = MAXV * MAXV;
struct edge {
  int v, c, nxt;
  edge() {}
  edge(int _v, int _c, int _nxt) : v(_v), c(_c), nxt(_nxt) {}
};
edge e[MAXE * 2 + 5];
int st[MAXV + 5], edge_cnt = 0;
inline void init() { memset(st, -1, sizeof st), edge_cnt = 0; }
inline int add_edge(int u, int v, int c, int type = 1) {
  int ret = edge_cnt;
  e[edge_cnt] = edge(v, c, st[u]), st[u] = edge_cnt++;
  if (type) add_edge(v, u, 0, 0);
  return ret;
}
int d[MAXV + 5], cur[MAXV + 5];
inline int BFS(int S, int T) {
  static queue<int> q;
  memset(d, -1, sizeof d);
  d[S] = 0, q.push(S);
  while (!q.empty()) {
    int u = q.front();
    cur[u] = st[u], q.pop();
    for (int i = st[u]; ~i; i = e[i].nxt) {
      int v = e[i].v;
      if (d[v] == -1 && e[i].c > 0) d[v] = d[u] + 1, q.push(v);
    }
  }
  return d[T] != -1;
}
inline int dfs(int u, int low, int T) {
  if (u == T) return low;
  int sum = 0;
  for (int &i = cur[u]; ~i; i = e[i].nxt) {
    int v = e[i].v, k;
    if (d[v] == d[u] + 1 && e[i].c > 0 &&
        (k = dfs(v, min(low, e[i].c), T)) > 0) {
      e[i].c -= k, e[i ^ 1].c += k;
      low -= k, sum += k;
      if (low == 0) break;
    }
  }
  return sum;
}
inline int solve(int S, int T) {
  int ans = 0;
  while (BFS(S, T))
    for (int k = dfs(S, INF, T); k > 0; k = dfs(S, INF, T)) ans += k;
  return ans;
}
}  // namespace FLOW
int n;
int G[MAXM + 5][MAXM + 5];
inline void input() {
  n = read<int>();
  for (int i = 1; i < n; ++i) {
    static char s[MAXM + 5];
    int u = read_str(s) - s, v = read_str(s) - s;
    if (u > v) swap(u, v);
    ++G[u][v];
  }
}
int M = 0;
static vector<int> ids[MAXM + 5];
vector<pair<int, int> > E, E0;
inline void build_tree(int S) {
  static int seq[MAXM + 5];
  for (int i = 1; i <= M - 2; ++i) seq[i] = S % M + 1, S /= M;
  static int cnt[MAXM + 5];
  static set<int> T;
  for (int i = 1; i <= M; ++i) T.insert(i);
  for (int i = 1; i <= M - 2; ++i) T.erase(seq[i]), ++cnt[seq[i]];
  for (int i = 1; i <= M - 2; ++i) {
    int u = *T.begin(), v = seq[i];
    T.erase(u);
    if (--cnt[v] == 0) T.insert(v);
    if (u > v) swap(u, v);
    --G[u][v], E0.emplace_back(u, v);
  }
  if (((int)(T).size()) >= 2) {
    int u = *T.begin(), v = *T.rbegin();
    T.clear();
    if (u > v) swap(u, v);
    --G[u][v], E0.emplace_back(u, v);
  }
}
int id[MAXM + 5][MAXM + 5], nd[MAXM + 5];
pair<int, int> eid[MAXM + 5][MAXM + 5];
inline int check() {
  for (int i = 1; i <= M; ++i)
    for (int j = i + 1; j <= M; ++j)
      if (G[i][j] < 0) return 0;
  FLOW::init();
  int cnt = 0, S = ++cnt, T = ++cnt;
  for (int i = 1; i <= M; ++i)
    FLOW::add_edge(nd[i] = ++cnt, T, ((int)(ids[i]).size()) - 1);
  int sum = 0;
  for (int i = 1; i <= M; ++i)
    for (int j = i + 1; j <= M; ++j)
      if (G[i][j]) {
        sum += G[i][j];
        FLOW::add_edge(S, id[i][j] = ++cnt, G[i][j]);
        eid[i][j].first = FLOW::add_edge(id[i][j], nd[i], INF);
        eid[i][j].second = FLOW::add_edge(id[i][j], nd[j], INF);
      }
  return FLOW::solve(S, T) == sum;
}
inline void output() {
  static int key[MAXM + 5];
  for (auto it : E) printf("%d %d\n", it.first, it.second);
  for (auto it : E0)
    printf("%d %d\n", ids[it.first].back(), ids[it.second].back());
  for (int i = 1; i <= M; ++i) key[i] = ids[i].back(), ids[i].pop_back();
  for (int i = 1; i <= M; ++i)
    for (int j = i + 1; j <= M; ++j)
      if (G[i][j]) {
        for (int t = FLOW::e[eid[i][j].first ^ 1].c; t--;)
          printf("%d %d\n", key[j], ids[i].back()), ids[i].pop_back();
        for (int t = FLOW::e[eid[i][j].second ^ 1].c; t--;)
          printf("%d %d\n", key[i], ids[j].back()), ids[j].pop_back();
      }
}
inline void cancel() {
  while (!E0.empty()) {
    int u = E0.back().first, v = E0.back().second;
    if (u > v) swap(u, v);
    ++G[u][v], E0.pop_back();
  }
}
inline void solve() {
  for (int i = 1; i <= n; ++i) ids[(int)log10(i) + 1].push_back(i);
  M = (int)log10(n) + 1;
  for (int i = 1; i <= M; ++i) {
    if (G[i][i] >= ((int)(ids[i]).size())) {
      puts("-1");
      return;
    }
    while (G[i][i]--) {
      int p = ids[i].back();
      ids[i].pop_back();
      E.emplace_back(ids[i].back(), p);
    }
  }
  for (int S = 0, T = (int)pow(M, M - 2); S < T; ++S) {
    build_tree(S);
    if (check()) {
      output();
      return;
    }
    cancel();
  }
  puts("-1");
}
int main() {
  input();
  solve();
  return 0;
}
