#include <bits/stdc++.h>
using namespace std;

#define all(x) (x).begin(), (x).end()

#define TRACE(x) x
#define WATCH(x) TRACE(cout << #x" = " << x << endl)
#define PRINT(x...) TRACE(printf(x))
#define WATCHR(a, b) TRACE(for (auto c=a; c!=b;) cout << *(c++) << " "; cout << endl)
#define WATCHC(V) TRACE({cout << #V" = "; WATCHR(V.begin(), V.end());})

#define FU(i, a, b) for (auto i = a; i < b; ++i)
#define fu(i, b) FU(i, 0, b)
#define FD(i, a, b) for (auto i = (b) - 1; i >= a; --i)
#define fd(i, b) FD(i, 0, b)

using ll = long long;
using vi = vector<int>;
using vvi = vector<vi>;
using vll = vector<ll>;
using vvll = vector<vll>;
using vd = vector<double>;
using vb = vector<bool>;
using pii = pair<int, int>;
using pll = pair<ll, ll>;

ll mod(ll a, ll b) {
  return ((a%b)+b)%b;
}

int cmp(double x, double y = 0, double tol = 1.e-7) {
	return (x <= y + tol) ? (x + tol < y) ? -1 : 0 : 1;
}
#define pb push_back
#define sz(x) ((int)(x).size())

struct graph {
  vi dest;  // use sz(dest) as number of arcs
  vvi adj;  // use sz(adj) as number of vertices
  int inv(int a) { return a ^ 0x1; }
  graph(int n = 0) {
    adj.resize(n);
  }
  // Adds an arc to the graph. u is capacity, c is cost.
  // u is only needed on flows, and c only on min-cost-flow
  int arc(int i, int j, ll u = 1) {
    dest.pb(j);
    adj[i].pb(sz(dest)-1);
    dest.pb(i);
    adj[j].pb(sz(dest)-1);
    cap.pb(u); // For both flows
    cap.pb(0);
    return sz(dest)-2;
  }
  //////////////////////////////////////////////////////////////////////////////
  // For both flows!!
  //

  vll cap, flow;

  int orig(int a) { return dest[inv(a)]; }
  ll capres(int a) { return cap[a] - flow[a]; }

  //////////////////////////////////////////////////////////////////////////////
  // Max Flow! - Dinic O(n^2 * m)
  // don't call maxflow with ini == end
  //

  vi curAdj, d;

  bool MFbfs(int s, int t) {
    d.assign(sz(adj), INT_MAX/2);
    curAdj = vi(sz(adj));
    d[s] = 0;
    queue<int> Q; Q.push(s);
    while (!Q.empty()) {
      int u = Q.front(); Q.pop();
      for (auto i : adj[u]) {
        int v = dest[i];
        if (capres(i) > 0 && d[v] == INT_MAX/2) {
          d[v] = d[u] + 1; Q.push(v);
        }
      }
    }
    return d[t] != INT_MAX/2;
  }

  ll MFdfs(int u, int t, ll f) {
    if (u == t) return f;
    for(int &i = curAdj[u]; i < adj[u].size(); ++i) {
      int ar = adj[u][i], v = dest[ar];
      if (d[v] != d[u]+1 || capres(ar) == 0) continue;
      ll tmpF = MFdfs(v, t, min(f, capres(ar)));
      if (tmpF) {
        flow[ar] += tmpF;
        flow[inv(ar)] -= tmpF;
        return tmpF;
      }
    }
    return 0;
  }

  ll maxflow(int ini, int end) {
    flow.assign(sz(dest), 0);
    while (MFbfs(ini, end))
      while (MFdfs(ini, end, LLONG_MAX/2));
    ll F = 0;
    for (int a : adj[ini]) F += flow[a];
    return F;
  }
};

int main() {
	int N;
	scanf("%d", &N);

	graph G(2 * N + 1);
	fu(i, N-1) G.arc(0, 1 + i, 1);
	fu(i, N) G.arc(N + i, 2 * N, 1);

	vvi inc(N);
	fu(i, N-1) {
		int c;
		scanf("%d", &c);
		while (c--) {
			int x;
			scanf("%d", &x); x--;
			G.arc(1 + i, N + x, 1);
			inc[x].push_back(i);
		}
	}

	if (G.maxflow(0, 2*N) != (N - 1)) {
		printf("-1\n");
		return 0;
	}

	// find the match of each set
	vi match(N-1);
	ll root = 0;
	fu(i, N) root += i;

	fu(i, N-1) {
		for (int a : G.adj[1 + i])
			if (G.flow[a] == 1) {
				match[i] = G.dest[a] - N;
				root -= G.dest[a] - N;
			}
	}

	queue<int> Q;
	Q.push(root);
	vb mark(N, false);
	mark[root] = true;

	vector<pair<int, int>> ans(N - 1);
	int tot = 0;
	while (!Q.empty()) {
		int x = Q.front(); Q.pop();
		tot++;

		for (int s : inc[x]) {
			int y = match[s];
			if (mark[y]) continue;
			mark[y] = true;
			Q.push(y);
			ans[s] = {x, y};
		}
	}

	if (tot != N) printf("-1\n");
	else for (auto p : ans) printf("%d %d\n", p.first + 1, p.second + 1);

	return 0;
}


Measured memory_bytes: 38104