//~ while (clock()<=69*CLOCKS_PER_SEC)
//~ #pragma comment(linker, "/stack:200000000")
#pragma GCC optimize("O3")
//~ #pragma GCC optimize("Ofast")
//~ #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,tune=native")
//~ #pragma GCC optimize("unroll-loops")
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
#include <ext/pb_ds/tree_policy.hpp>

using namespace __gnu_pbds;
using namespace std;

template <typename T>
using ordered_set =
    tree<T, null_type, less<T>, rb_tree_tag, tree_order_statistics_node_update>;

#define sim template < class c
#define ris return * this
#define dor > debug & operator <<
#define eni(x) sim > typename \
  enable_if<sizeof dud<c>(0) x 1, debug&>::type operator<<(c i) {
sim > struct rge { c b, e; };
sim > rge<c> range(c i, c j) { return rge<c>{i, j}; }
sim > auto dud(c* x) -> decltype(cerr << *x, 0);
sim > char dud(...);
struct debug {
#ifdef LOCAL
~debug() { cerr << endl; }
eni(!=) cerr << boolalpha << i; ris; }
eni(==) ris << range(begin(i), end(i)); }
sim, class b dor(pair < b, c > d) {
  ris << "(" << d.first << ", " << d.second << ")";
}
sim dor(rge<c> d) {
  *this << "[";
  for (auto it = d.b; it != d.e; ++it)
    *this << ", " + 2 * (it == d.b) << *it;
  ris << "]";
}
#else
sim dor(const c&) { ris; }
#endif
};
#define imie(...) " [" << #__VA_ARGS__ ": " << (__VA_ARGS__) << "] "

#define shandom_ruffle random_shuffle

using ll=long long;
using pii=pair<int,int>;
using pll=pair<ll,ll>;
using vi=vector<int>;
using vll=vector<ll>;
const int nax=200*1007;

using T = int;
class Flow {
  struct E {
    int dest;
    T orig, *lim, *rev;
  };
  int zr, uj, n = 0;
  vector<unique_ptr<T>> ts;
  vector<vector<E>> graf;
  vector<int> ptr, odl;

  void vert(int v) {
    n = max(n, v + 1);
    graf.resize(n);
    ptr.resize(n);
    odl.resize(n);
  }

  bool iszero(T v) {
    return !v; // Zmienić dla doubli.
  }

  void bfs() {
    fill(odl.begin(), odl.end(), 0);
    vector<int> kol = {zr};
    odl[zr] = 1;
    for (int i = 0; i < (int) kol.size(); i++) {
      for (E& e : graf[kol[i]]) {
        if (!odl[e.dest] and !iszero(*e.lim)) {
          odl[e.dest] = odl[kol[i]] + 1;
          kol.push_back(e.dest);
        }
      }
    }
  }

  T dfs(int v, T lim) {
    if (v == uj) return lim;
    T ret = 0, wez;
    for (int& i = ptr[v]; i < (int) graf[v].size(); i++) {
      E& e = graf[v][i];
      if (odl[e.dest] == odl[v] + 1 and !iszero(*e.lim) and
          !iszero(wez = dfs(e.dest, min(*e.lim, lim)))) {
        ret += wez;
        *e.lim -= wez;
        *e.rev += wez;
        lim -= wez;
        if (iszero(lim)) break;
      }
    }
    return ret;
  }

 public:
  void add_edge(int u, int v, T lim, bool bi = false /* bidirectional? */) {
    vert(max(u, v));
    T *a = new T(lim), *b = new T(lim * bi);
    ts.emplace_back(a);
    ts.emplace_back(b);
    graf[u].push_back(E{v, lim,      a, b});
    graf[v].push_back(E{u, lim * bi, b, a});
  }

  T dinic(int zr_, int uj_) {
    zr = zr_; uj = uj_;
    vert(max(zr, uj));
    T ret = 0;
    while (true) {
      bfs();
      fill(ptr.begin(), ptr.end(), 0);
      const T sta = dfs(zr, numeric_limits<T>::max());  // Dla doubli można dać
      if (iszero(sta)) break;                           // infinity() zamiast
      ret += sta;                                       // max().
    }
    return ret;
  }

  vector<int> cut() {
    vector<int> ret;
    bfs();
    for (int i = 0; i < n; i++)
      if (odl[i])
        ret.push_back(i);
    return ret;
  }

  map<pair<int, int>, T> get_flowing() {  // Tam gdzie plynie 0 może nie być
    map<pair<int, int>, T> ret;           // krawędzi.
    for (int i = 0; i < n; i++)
      for (E& e : graf[i])
        if (*e.lim < e.orig)
          ret[make_pair(i, e.dest)] += e.orig - *e.lim;
    for (auto& i : ret) {
      const pair<int, int> rev{i.first.second, i.first.first};
      const T x = min(i.second, ret[rev]);
      i.second -= x;
      ret[rev] -= x;
    }
    return ret;
  }
};


int n;
vi wek[nax];

Flow janusz;

vi nalezy[nax];

int sko[nax];
int prze[nax];

int oj[nax];

int ile[nax];

void nope()
{
	printf("-1\n");
	exit(0);
}

int fin(int v)
{
	if (oj[v]!=v)
		oj[v]=fin(oj[v]);
	return oj[v];
}

pii kra[nax];

void uni(int a, int b, int kt)
{
	if (fin(a)==fin(b))
		nope();
	kra[kt]={a, b};
	oj[fin(a)]=fin(b);
}

int bylo[nax];

void dfs(int v)
{
	if (bylo[v])
		return;
	bylo[v]=1;
	debug() << imie(v);
	for (int i : nalezy[v])
	{
		if (kra[i].first)
			continue;
		uni(v, prze[i], i);
		dfs(prze[i]);
	}
}

int main()
{
	scanf("%d", &n);
	for (int i=1; i<=n; i++)
		oj[i]=i;
	for (int i=1; i<n; i++)
	{
		int x, y;
		scanf("%d", &x);
		while(x--)
		{
			scanf("%d", &y);
			wek[i].push_back(y);
			nalezy[y].push_back(i);
		}
		ile[i]=wek[i].size();
		//~ debug() << wek[i];
	}
	for (int i=1; i<n; i++)
		janusz.add_edge(0, i, 1);
	for (int i=1; i<=n; i++)
		janusz.add_edge(n+i, n, 1);
	for (int i=1; i<n; i++)
		for (int j : wek[i])
			janusz.add_edge(i, n+j, 1);
	janusz.dinic(0, n);
	auto wez=janusz.get_flowing();
	debug() << wez;
	for (auto i : wez)
		if (i.first.first>0 && i.first.first<n && i.first.second>n && i.second)
			sko[i.first.second-n]=i.first.first;
	for (int i=1; i<=n; i++)
		prze[sko[i]]=i;
	int v=0;
	debug() << range(prze+1, prze+1+n);
	for (int i=1; i<=n; i++)
		if (!sko[i])
			v=i;
	debug() << imie(v);
	dfs(v);
	for (int i=1; i<n; i++)
		if (!kra[i].first)
			nope();
	for (int i=1; i<n; i++)
		printf("%d %d\n", kra[i].first, kra[i].second);
	return 0;
}


Measured memory_bytes: 127940