// includes {{{
#include<iostream>
#include<iomanip>
#include<algorithm>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#include<tuple>
#include<cmath>
#include<random>
#include<cassert>
// #include<deque>
// #include<multiset>
// #include<bitset>
// #include<cstring>
// #include<bits/stdc++.h>
// }}}
using namespace std;
using ll = long long;

// constructor(n, inf?) // be careful !
// addEdge(from, to, capacity, isDirected? = false)
//   - returns edgeID
// === build(s, t) - returns max flow (or inf) ===
// === restoreMinCut(s) ===
// .isCut[edgeID]
// === --- ===
// inf * 2 < LL_MAX
/// --- Max Flow with Dinic Library {{{ ///

struct Dinic {
  struct Edge {
    int from, to;
    ll cap, rev;
    int To(int i) const { return from == i ? to : from; }
    ll& Cap(int i) { return from == i ? cap : rev; }
    ll& Rev(int i) { return from == i ? rev : cap; }
  };

  int n;
  vector< Edge > edges;
  vector< vector< int > > g;
  ll inf;
  Dinic(int n, ll inf = 1e18) : n(n), g(n), inf(inf) {}
  int addEdge(int a, int b, ll cap, bool undirected = false) {
    edges.emplace_back((Edge){a, b, cap, undirected ? cap : 0});
    g[a].emplace_back(edges.size() - 1);
    g[b].emplace_back(edges.size() - 1);
    return edges.size() - 1;
  }
  vector< int > level;
  vector<size_t> itr;
  ll build(int s, int t) {
    level.resize(n);
    itr.resize(n);
    ll flow = 0, newflow;
    while(bfs(s), level[t] >= 0) {
      itr.assign(n, 0);
      while((newflow = dfs(s, t, inf)) > 0) {
        flow += newflow;
        if(flow >= inf) return inf;
      }
    }
    return flow;
  }
  vector< int > isCut;
  void restoreMinCut(int s) {
    isCut = vector< int >(edges.size());
    // bfs
    vector< int > used(n);
    queue< int > q;
    q.emplace(s);
    used[s] = 1;
    while(q.size()) {
      int i = q.front();
      q.pop();
      for(int idx : g[i]) {
        Edge& edge = edges[idx];
        if(!used[edge.To(i)] && edge.Cap(i) > 0) {
          q.emplace(edge.To(i));
          used[edge.To(i)] = 1;
        }
      }
    }
    for(size_t i = 0; i < edges.size(); i++) {
      if(used[edges[i].from] != used[edges[i].to]) isCut[i] = 1;
    }
  }

private:
  void bfs(int s) {
    fill(begin(level), end(level), -1);
    queue< int > q;
    q.emplace(s);
    level[s] = 0;
    while(q.size()) {
      int i = q.front();
      q.pop();
      for(int idx : g[i]) {
        Edge edge = edges[idx];
        if(level[edge.To(i)] < 0 && edge.Cap(i) > 0) {
          level[edge.To(i)] = level[i] + 1;
          q.emplace(edge.To(i));
        }
      }
    }
  }

  ll dfs(int v, int t, ll flow) {
    if(v == t) return flow;
    for(size_t &i = itr[v]; i < g[v].size(); ++i) {
      Edge& edge = edges[g[v][i]];
      if(edge.Cap(v) > 0 && level[edge.To(v)] > level[v]) {
        ll newflow = dfs(edge.To(v), t, min(flow, edge.Cap(v)));
        if(newflow == 0) continue;
        edge.Cap(v) -= newflow;
        edge.Rev(v) += newflow;
        return newflow;
      }
    }
    return 0;
  }
};

/// }}}--- ///

const ll inf = 1e9;

int n;

void imp() {
  cout << -1 << endl;
  exit(0);
}

int ans[112345][2];

vector<int> inc[112345]; // [x] := xを要素に持つグループら

vector<int> usedGroup(112345);

void dfs(int i) {
  for(int gid : inc[i]) if(!usedGroup[gid]) {
    usedGroup[gid] = 1;
    dfs(ans[gid][0]);
    ans[gid][1] = i;
  }
}

// メモ：
// k個のグループにk頂点しか無い場合は自明にループができるので不可
// よって (k+1) 頂点以上に繋がってていなきゃいけない
// そのとき k 頂点に必ず繋がっていることになる
// これは結婚定理と同じ条件なので，必ずn-1本のマッチングが存在することになる

// すると E_i has i を必ず達成できるように番号付けできる
// E_0 + E_1 + E_2 + ... + E_k = {0, 1, ..., k} が成り立ってはいけない，と言い換えられる
// どのような頂点からはじめても，頂点Nにたどり着けなければならない
// 頂点Nから逆にたどってやって，全てにたどり着けるかだけ調べればいい

// その仮定で，そのDFS木をそのまま構成する木にできる

int main() {
  ios::sync_with_stdio(false), cin.tie(0);
  cin >> n;
  Dinic eca(n + n - 1 + 2, inf);
  int s = n + n - 1 + 0, t = s + 1;
  vector<tuple<int, int, int>> v;
  for(int i = 0; i < n - 1; i++) {
    int c; cin >> c;
    for(int j = 0; j < c; j++) {
      int x;
      cin >> x;
      x--;
      inc[x].emplace_back(i);
      int id = eca.addEdge(i, n - 1 + x, 1);
      v.emplace_back(id, i, x);
    }
    eca.addEdge(s, i, 1);
    eca.addEdge(n - 1 + i, t, 1);
  }
  eca.addEdge(n - 1 + n - 1, t, 1);


  int sz = eca.build(s, t);
  if(sz != n - 1) imp();

  vector<int> found(n);
  for(auto p : v) {
    int id, from, to;
    tie(id, from, to) = p;
    if(eca.edges[id].cap == 0) {
      ans[from][0] = to;
      found[to] = 1;
    }
  }

  int root;
  for(int i = 0; i < n; i++) if(!found[i]) root = i;

  dfs(root);

  for(int i = 0; i < n - 1; i++) if(!usedGroup[i]) imp();

  for(int i = 0; i < n - 1; i++) {
    cout << ans[i][0] + 1 << " " << ans[i][1] + 1 << "\n";
  }

  return 0;
}


Measured memory_bytes: 38696