#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(i,n) for(int i=(0);i<(n);i++)
#define qwq(x) cerr<<"# "<<#x<<" = "<<x<<endl
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define bits bitset<N>
using namespace std;
typedef unsigned long long u64;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

template<class T> inline void read(T &x){
	int f=0;x=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())f|=(ch=='-');
	for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	if(f)x=-x;
}

const int N=1005;
int mark[N],vis[N],cur[N],n,m,cnt,a,b;
map<pii,int> appear;
bits s[N],nei[N];
vector<pii> ans;
vi e[N];
/*
7
4 1 2 3 4
6 1 2 3 4 5 7
7 1 2 3 4 5 6 7
4 1 2 3 4
5 2 3 5 6 7
3 3 5 6
4 2 3 5 7

*/
int main(){
	read(n);
	if(n==2){
		puts("1 2");
		return 0;
	}
	rep(i,1,n){
		read(m);
		rep(j,1,m){
			read(a);
			s[i].set(a);
		}
	}
	rep(i,1,n)rep(j,i+1,n){
		bits t=s[i]&s[j];
		if(t.count()==2){
			int a=t._Find_first();
			int b=t._Find_next(a);
			if(appear[mp(a,b)])continue;
			appear[mp(a,b)]=1;
			appear[mp(b,a)]=1;
			mark[a]=1,mark[b]=1,cnt++;
			nei[a].set(a),nei[b].set(b);
			nei[a].set(b),nei[b].set(a);
			ans.pb(mp(a,b));
//			printf("link %d %d\n",a,b);
		}
	}
	if(cnt==0){
		rep(i,2,n)
			printf("1 %d\n",i);
		return 0;
	}
	rep(i,1,n)cur[i]=i;
	hash<bits> fun;
	sort(cur+1,cur+n+1,[&](int a,int b){
		int A=s[a].count(),B=s[b].count();
		if(A!=B)return A<B;
		return fun(s[a])<fun(s[b]);
	});
	for(int l=1,r;l<=n;l++){
		r=l;
		while(r<n&&s[cur[l]]==s[cur[r+1]])r++;
//		printf("doit %d %d   %d\n",l,r,cur[l]);
		int p=cur[l],o=0,cnt=0,t;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i)){
//			printf("@@ %d %d   %d  %d  %d\n",o,i,mark[i]&&!vis[i]&&(s[p]&nei[i])==nei[i],nei[i].count(),nei[o].count());
			if(mark[i]&&!vis[i]&&(s[p]&nei[i])==nei[i]&&(!o||nei[i].count()>nei[o].count())){
//				printf("@@@ %d %d\n",o,i);
				o=i;
			}
			if(!mark[i]&&!vis[i])++cnt;
		}
		if(r-l+1!=cnt)continue;
		assert(o);
		vis[o]=1;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i))
			if(!mark[i]&&!vis[i]){
				ans.pb(mp(o,i));
//				printf("Link %d %d\n",o,i);
				vis[i]=1;
			}
		l=r;
	}
	for(auto x:ans)
		printf("%d %d\n",x.fi,x.se);
	assert(ans.size()==n-1);
	return 0;
}


#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(i,n) for(int i=(0);i<(n);i++)
#define qwq(x) cerr<<"# "<<#x<<" = "<<x<<endl
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define bits bitset<N>
using namespace std;
typedef unsigned long long u64;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

template<class T> inline void read(T &x){
	int f=0;x=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())f|=(ch=='-');
	for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	if(f)x=-x;
}

const int N=1005;
int mark[N],vis[N],cur[N],n,m,cnt,a,b;
map<pii,int> appear;
bits s[N],nei[N];
vector<pii> ans;
vi e[N];

int main(){
	read(n);
	if(n==2){
		puts("1 2");
		return 0;
	}
	rep(i,1,n){
		read(m);
		rep(j,1,m){
			read(a);
			s[i].set(a);
		}
	}
	rep(i,1,n)rep(j,i+1,n){
		bits t=s[i]&s[j];
		if(t.count()==2){
			int a=t._Find_first();
			int b=t._Find_next(a);
			if(appear[mp(a,b)])continue;
			appear[mp(a,b)]=1;
			appear[mp(b,a)]=1;
			mark[a]=1,mark[b]=1,cnt++;
			nei[a].set(a),nei[b].set(b);
			nei[a].set(b),nei[b].set(a);
			ans.pb(mp(a,b));
		}
	}
	if(cnt==0){
		rep(i,2,n)printf("1 %d\n",i);
		return 0;
	}
	rep(i,1,n)cur[i]=i;
	hash<bits> fun;
	sort(cur+1,cur+n+1,[&](int a,int b){
		int A=s[a].count(),B=s[b].count();
		if(A!=B)return A<B;
		return fun(s[a])<fun(s[b]);
	});
	for(int l=1,r;l<=n;l++){
		r=l;
		while(r<n&&s[cur[l]]==s[cur[r+1]])r++;
		int p=cur[l],o=0,cnt=0,t;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i)){
			if(mark[i]&&!vis[i]&&(s[p]&nei[i])==nei[i]
			&&(!o||nei[i].count()>nei[o].count())) o=i;
			if(!mark[i]&&!vis[i])++cnt;
		}
		if(r-l+1!=cnt)continue;
		vis[o]=1;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i))
			if(!mark[i]&&!vis[i]){
				ans.pb(mp(o,i));
				vis[i]=1;
			}
		l=r;
	}
	for(auto x:ans)
		printf("%d %d\n",x.fi,x.se);
	return 0;
}


#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define REP(i,n) for(int i=(0);i<(n);i++)
#define qwq(x) cerr<<"# "<<#x<<" = "<<x<<endl
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define bits bitset<N>
using namespace std;
typedef unsigned long long u64;
typedef pair<int,int> pii;
typedef vector<int> vi;
typedef long long ll;

template<class T> inline void read(T &x){
	int f=0;x=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())f|=(ch=='-');
	for(;isdigit(ch);ch=getchar())x=x*10+ch-'0';
	if(f)x=-x;
}

const int N=1005;
int mark[N],vis[N],cur[N],n,m,cnt,a,b;
map<pii,int> appear;
bits s[N],nei[N];
vector<pii> ans;
vi e[N];
/*
7
3 1 2 3
5 1 2 3 4 7
7 1 2 3 4 5 6 7
6 2 3 4 5 6 7
4 3 4 5 6
4 3 4 5 6
4 2 3 4 7
*/
int main(){
	read(n);
	if(n==2){
		puts("1 2");
		return 0;
	}
	rep(i,1,n){
		read(m);
		rep(j,1,m){
			read(a);
			s[i].set(a);
		}
	}
	rep(i,1,n)rep(j,i+1,n){
		bits t=s[i]&s[j];
		if(t.count()==2){
			int a=t._Find_first();
			int b=t._Find_next(a);
			if(appear[mp(a,b)])continue;
			appear[mp(a,b)]=1;
			appear[mp(b,a)]=1;
			mark[a]=1,mark[b]=1,cnt++;
			nei[a].set(a),nei[b].set(b);
			nei[a].set(b),nei[b].set(a);
			ans.pb(mp(a,b));
//			printf("link %d %d\n",a,b);
		}
	}
	if(cnt==0){
		rep(i,2,n)
			printf("1 %d\n",i);
		return 0;
	}
	rep(i,1,n)cur[i]=i;
	hash<bits> fun;
	sort(cur+1,cur+n+1,[&](int a,int b){
		int A=s[a].count(),B=s[b].count();
		if(A!=B)return A<B;
		return fun(s[a])<fun(s[b]);
	});
	for(int l=1,r;l<=n;l++){
		r=l;
		while(r<n&&s[cur[l]]==s[cur[r+1]])r++;
//		printf("doit %d %d   %d\n",l,r,cur[l]);
		int p=cur[l],o=0,cnt=0,t;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i)){
			if(mark[i]&&!vis[i]&&(s[p]&nei[i])==nei[i]&&(!o||nei[i].count()>nei[o].size()))o=i;
			if(!mark[i]&&!vis[i])++cnt;
		}
		if(r-l+1!=cnt)continue;
		assert(o);
		vis[o]=1;
		for(int i=s[p]._Find_first();i<s[p].size();i=s[p]._Find_next(i))
			if(!mark[i]&&!vis[i]){
				ans.pb(mp(o,i));
//				printf("Link %d %d\n",o,i);
				vis[i]=1;
			}
		l=r;
	}
	for(auto x:ans)
		printf("%d %d\n",x.fi,x.se);
	assert(ans.size()==n-1);
	return 0;
}


#include <iostream>
#include <cstdlib>
#include <algorithm>

using namespace std;

const int limite=1001;

int n;
int pertenece[limite][limite];
int hijo[limite][limite];
int shijo[limite][limite];
int hijo2[limite][limite];
int set[limite];
int padre[limite];

void genera(int u)
{
  //cout<<"genera "<<u<<" "<<set[u]<<endl;
  /*
  cout<<"hijos ";
  for (int v=1;v<=n;v++)
    if (hijo[u][v])
      cout<<v<<",";
  cout<<endl;
  cout<<"setshijos ";
  for (int s=1;s<=n;s++)
    if (shijo[u][s])
      cout<<s<<",";
  cout<<endl;
  */
  bool hayhijo2=false;
  for (int w=1;w<=n;w++) {
    if (w!=u and not hijo[u][w] and pertenece[w][set[u]]) {
      hijo2[u][w]=1;
      hayhijo2=true;
    }
  }
  if (not hayhijo2) return;
  for (int t=1;t<=n;t++) {
    if (t!=set[u] and not shijo[u][t] and pertenece[u][t]) {
      //cout<<"encontramos set "<<t<<endl;
      int hijou=0;
      for (int v=1;v<=n and hijou==0;v++)
	if (pertenece[v][t] and hijo[u][v])
	  hijou=v;
      //cout<<"hijou "<<hijou<<endl;
      for (int w=1;w<=n;w++) {
	if (pertenece[w][t] and hijo2[u][w]) {
	  if (hijo[hijou][w]==0) {
	    hijo[hijou][w]=1;
	    cout<<hijou<<" "<<w<<endl;
	    padre[w]=hijou;
	  }
	  shijo[hijou][t]=1;
	}
      }
    }
  }
  for (int s=1;s<=n;s++) {
    if (shijo[u][s]) {
      //cout<<"analizando set "<<s<<endl;
      int hijou=0;
      for (int w=1;w<=n and hijou==0;w++) {
	if (pertenece[w][s] and hijo2[u][w]) {
	  //cout<<"asignamos w padrew "<<w<<" "<<padre[w]<<endl;
	  hijou=padre[w];
	}
      }
      if (hijou!=0)
	set[hijou]=s;
      //cout<<"set "<<hijou<<" "<<s<<endl;
      for (int v=1;v<=n;v++)
	if (hijo[u][v])
	  pertenece[v][s]=0;
    }
  }
  for (int v=1;v<=n;v++)
    if (hijo[u][v])
      pertenece[v][set[u]]=0;
  for (int s=1;s<=n;s++)
    pertenece[u][s]=0;
  for (int v=1;v<=n;v++)
    if (hijo[u][v] and set[v])
      genera(v);
}

int tamanyo[limite];
int bset[limite][40];
int rando[limite];
int puntos[limite];
int veo[limite];
int ocurrencias[limite];

bool incluido(int s1,int s2)
{
  int (&set1)[40]=bset[s1];
  int (&set2)[40]=bset[s2];
  for (int i=0;i<40;i++)
    if ((set1[i]&set2[i])!=set1[i])
      return false;
  return true;
}

int main()
{
  ios::sync_with_stdio(false);
  cin>>n;
  int numtamanyon=0;
  for (int s=1;s<=n;s++) {
    cin>>tamanyo[s];
    numtamanyon+=tamanyo[s]==n;
    for (int i=0;i<tamanyo[s];i++) {
      int u;
      cin>>u;
      pertenece[u][s]=1;
      ocurrencias[u]++;
      bset[s][u/30]|=1<<(u%30);
    }
  }

  // Casos especiales:

  if (n==1) exit(0);
  if (numtamanyon==n) {
    for (int u=2;u<=n;u++)
      cout<<1<<" "<<u<<endl;
    exit(0);
  }
  if (numtamanyon==2) {
    int u1=0,u2=0;
    for (int u=1;u<=n;u++) {
      if (ocurrencias[u]==n) {
	if (u1==0) u1=u;
	else u2=u;
      }
    }
    cout<<u1<<" "<<u2<<endl;
    for (int s=1;s<=n;s++) {
      if (tamanyo[s]!=n) {
	for (int u=1;u<=n;u++) {
	  if (u!=u1 and u!=u2) {
	    if (pertenece[u][s]) cout<<u1<<" "<<u<<endl;
	    else cout<<u2<<" "<<u<<endl;
	  }
	}
	exit(0);
      }
    }
  }

  // Caso general:
  //cout<<"caso general"<<endl;

  for (int u=1;u<=n;u++)
    rando[u]=u;
  random_shuffle(rando+1,rando+n+1);
  int hoja=0,shoja;
  for (int i=1;i<=n and hoja==0;i++) {
    int u=rando[i];
    int tamanyomin=10000;
    int smin;
    for (int s=1;s<=n;s++) {
      if (pertenece[u][s] and tamanyo[s]<tamanyomin) {
	//cout<<"tamanyo "<<s<<" "<<tamanyo[s]<<endl;
	tamanyomin=tamanyo[s];
	smin=s;
      }
    }
    //cout<<"smin "<<u<<" "<<smin<<endl;
    bool eshoja=true;
    for (int s=1;s<=n and eshoja;s++)
      if (pertenece[u][s])
	eshoja=incluido(smin,s);
    if (eshoja) {
      hoja=u;
      shoja=smin;
    }
  }
  set[hoja]=shoja;

  //cout<<"hoja shoja: "<<hoja<<" "<<shoja<<endl;

  for (int u=1;u<=n;u++)
    if (u!=hoja and pertenece[u][shoja])
      hijo2[hoja][u]=1;
  for (int s=1;s<=n;s++) {
    if (not pertenece[hoja][s]) {
      int u1=0,u2=0,cuantos=0;
      for (int u=1;u<=n;u++) {
	if (pertenece[u][s] and hijo2[hoja][u]) {
	  cuantos++;
	  if (u1==0) u1=u;
	  else u2=u;
	}
      }
      if (cuantos==2) {
	puntos[u1]++;
	puntos[u2]++;
      } else if (cuantos==1) {
	puntos[u1]--;
      }
    }
  }
  int hijohoja,puntoshijohoja=0;
  for (int u=1;u<=n;u++) {
    if (puntos[u]>puntoshijohoja) {
      hijohoja=u;
      puntoshijohoja=puntos[u];
    }
  }
  hijo[hoja][hijohoja]=1;
  hijo2[hoja][hijohoja]=0;

  cout<<hoja<<" "<<hijohoja<<endl;

  for (int u=1;u<=n;u++) {
    if (hijo2[hoja][u]) {
      hijo[hijohoja][u]=1;
      cout<<hijohoja<<" "<<u<<endl;
    }
  }

  for (int s=1;s<=n;s++) {
    if (not pertenece[hoja][s] and pertenece[hijohoja][s]) {
      int h2=0;
      for (int w=1;w<=n and h2==0;w++)
	if (pertenece[w][s] and hijo2[hoja][w])
	  h2=w;
      for (int w=1;w<=n;w++)
	if (pertenece[w][s] and w!=hijohoja and not hijo2[hoja][w])
	  veo[w]=h2;
    }
  }
  int shijohoja=0;
  for (int s=1;s<=n and shijohoja==0;s++) {
    if (s!=shoja and pertenece[hoja][s]) {
      int veo1=0;
      for (int u=1;u<=n and shijohoja==0;u++) {
	if (pertenece[u][s] and veo[u]) {
	  if (veo1 and veo[u]!=veo1)
	    shijohoja=s;
	  else
	    veo1=veo[u];
	}
      }
    }
  }
  if (shijohoja==0) {
    int minimo=10000;
    for (int s=1;s<=n;s++) {
      if (tamanyo[s]>tamanyo[shoja] and pertenece[hoja][s] and tamanyo[s]<minimo) {
	minimo=tamanyo[s];
	shijohoja=s;
      }
    }
  }

  //cout<<"shijohoja: "<<shijohoja<<endl;

  set[hijohoja]=shijohoja;
  for (int s=1;s<=n;s++)
    if (s!=shoja and s!=shijohoja and pertenece[hoja][s])
      shijo[hijohoja][s]=1;
  for (int s=1;s<=n;s++)
    pertenece[hoja][s]=0;
  for (int u=1;u<=n;u++)
    pertenece[u][shoja]=0;
  pertenece[hijohoja][shijohoja]=0;
  genera(hijohoja);
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].sec);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
//			for(int k=1;k<=n;++k) if(vis[k]&&num[u][k]<3) num[u][k]++;
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

//	for(int i=1;i<=n;++i,puts(""))
//		for(int j=1;j<=n;++j)
//			printf("(%d,%d) ",num[i][j],num[i][j]==3?2:(5-num[i][j]));

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<a.size();++i) {
			for(int j=0;j<b.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0)),G[j].PB(MP(i,0));
			else G[i].PB(MP(j,1)),G[j].PB(MP(i,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
//	for(int i=1;i<=n;++i) printf("%d ",col[i]); puts("");
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
	assert(edgetot==n-1);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].fir);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i) {
			for(int j=0;j<a.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0));
			else G[i].PB(MP(j,1));
		}
	memset(col,-1,sizeof(col));
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,1);

	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
		if(num[i][j]==3&&col[i]!=col[j]) printf("%d %d\n",i,j);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].fir);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i) {
			for(int j=0;j<a.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0));
			else G[i].PB(MP(j,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
	assert(edgetot==n-1);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].fir);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i) {
			for(int j=0;j<a.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0)),G[j].PB(MP(i,0));
			else G[i].PB(MP(j,1)),G[j].PB(MP(i,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
	assert(edgetot==n-1);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].sec);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
//			for(int k=1;k<=n;++k) if(vis[k]&&num[u][k]<3) num[u][k]++;
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

//	for(int i=1;i<=n;++i,puts(""))
//		for(int j=1;j<=n;++j)
//			printf("(%d,%d) ",num[i][j],num[i][j]==3?2:(5-num[i][j]));

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i)
			for(int j=0;j<a.size();++j)
				if(num[b[i]][a[j]]==3) { printf("%d %d\n",b[i],a[j]); break; }
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0)),G[j].PB(MP(i,0));
			else G[i].PB(MP(j,1)),G[j].PB(MP(i,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
//	for(int i=1;i<=n;++i) printf("%d ",col[i]); puts("");
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
//	assert(edgetot==n-1);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].fir);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i) {
			for(int j=0;j<a.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0));
			else G[i].PB(MP(j,1));
		}
	memset(col,-1,sizeof(col));
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,1);

	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
		if(num[i][j]==3&&col[i]!=col[j]) printf("%d %d\n",i,j);
	return 0;
}


#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];

vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].fir);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}

	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
			for(int k=1;k<=n;++k) if(vis[k]&&num[u][k]<3) num[u][k]++;
//			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
//				num[u][k]++;
//				if(num[u][k]==3) nxt[u][k]=k+1;
//			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}

	if(n==2) {
		printf("1 2\n");
		return 0;
	}

	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;

	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i) {
			for(int j=0;j<a.size();++j)
				if(num[a[i]][b[j]]==3) { printf("%d %d\n",a[i],b[j]); break; }
		}
		return 0;
	}

	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0)),G[j].PB(MP(i,0));
			else G[i].PB(MP(j,1)),G[j].PB(MP(i,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
	assert(edgetot==n-1);
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N];

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N], Leaf[Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		Leaf[i] = true;
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	int edge = 0;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), Leaf[p] = Leaf[q] = false, ++edge;
			}
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Dis1[j].set(i);
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (!Leaf[j] && !(Dis1[j] != Near[i]))
				{
					print(i), putc(' '), print(j), putc('\n'), ++edge;
					break;
				}
	if (edge != N - 1)
		while (true)
			;
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[Max_N / 64 + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N];

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (Find.popcount() != 2)
		return false;
	for (int x = 0, u;x <= (N >> 6);++x)
		if (Find.B[x])
			for (ULL b = 0ULL;b <= 63ULL;++b)
				if (Find.B[x] & (1ULL << b))
				{
					u = (x << 6) + b;
					if (A == 0)
						A = u;
					else
						B = u;
				}
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N], Leaf[Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	for (int i = 1, k;i <= N;++i)
	{
		Leaf[i] = true;
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), Leaf[p] = Leaf[q] = false;
			}
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Dis1[j].set(i);
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (!Leaf[j] && !(Dis1[j] != Near[i]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline Bitset operator-(const Bitset &b) const
	{
		Bitset c;
		for (int i = 0;i <= (N >> 6);++i)
			c.B[i] = B[i] - b.B[i];
		return c;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q);
			}
	for (int i = 1;i <= N;++i)
		if (leaf.qry(i))
			for (int j = 1;j <= N;++j)
				if (!leaf.qry(j) && Near[i].have(Dis1[j]) && leaf.have(Near[i] - Dis1[j]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset operator-(const Bitset &b) const
	{
		Bitset c;
		for (int i = 0;i <= (N >> 6);++i)
			c.B[i] = B[i] - b.B[i];
		return c;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q);
			}
	for (int i = 1;i <= N;++i)
		if (leaf.qry(i))
			for (int j = 1;j <= N;++j)
				if (!leaf.qry(j) && Near[i].have(Dis1[j]) && leaf.have(Near[i] - Dis1[j]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void onset(int pos)
	{
		B[pos >> 6] &= ((~0ULL) ^ (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N];

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N], Leaf[Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		Leaf[i] = true;
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), Leaf[p] = Leaf[q] = false;
			}
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Near[j].onset(i);
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (!Leaf[j] && !(Dis1[j] != Near[i]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <bits/stdc++.h>
#define y1 y1_
#define index index_
#define pipe pipe_
#define next next_
#define ll long long
#define Pi acos(-1.0)
#define lowbit(x) ((x&(-x)))
#define pb push_back
#define mk make_pair
#define pii pair<int,int>
#define fst first
#define scd second
using namespace std;
inline ll read() {
    ll f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
const int MAXN=1005;
const int INF=1e9;
bitset<MAXN> G[MAXN],S[MAXN],nl;
int n,fa[MAXN];
int get_fa(int x) {
    if(fa[x]==x) return x;
    return fa[x]=get_fa(fa[x]);
}
int main() {
//  freopen("","r",stdin);
//  freopen("","w",stdout);
    ios::sync_with_stdio(0);cin.tie(0);/*syn加速*/
    n=read();
    for(int i=1;i<=n;++i) {
        int k=read(); while(k--) {int x=read();G[i][x]=1;}
    }
    for(int i=1;i<=n;++i) fa[i]=i;
    bitset<MAXN> t; int u,v,cnt=0;
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=n;++j) {
            t=(G[i]&G[j]);
            if(t.count()==2) {
                u=0;v=0;
                for(int k=1;k<=n;++k) if(t[k]) (!u)?(u=k):(v=k);
                int fu=get_fa(u),fv=get_fa(v);
                if(fu!=fv) {
                    fa[fu]=fv;
                    cout<<u<<" "<<v<<endl; cnt++;
                    S[u][u]=S[v][v]=S[u][v]=S[v][u]=1;
                    nl[u]=nl[v]=1;/*not leaf*/
                }
            }
        }
    }
    if(cnt==0) {
        for(int i=2;i<=n;++i) cout<<1<<" "<<i<<endl;
        return 0;
    } else if(cnt==1) {
        for(int i=1;i<=n;++i) {
            if(G[i].count()<n) {/*叶子结点的邻居集合*/
                for(int j=1;j<=n;++j) {
                    if(!nl[j]) cout<<(G[i][j]?u:v)<<" "<<j<<endl;
                }
                break;
            }
        }
        return 0;
    }

    for(int i=1;i<=n;++i) {
        if(!nl[i]) {
            int mn=INF;
            for(int j=1;j<=n;++j) {
                if(G[j][i]&&(G[j]&nl).count()<mn) {
                    t=(G[j]&nl);mn=t.count();
                }
            }
            for(int j=1;j<=n;++j) {
                if(t[j]&&S[j]==t) {
                    cout<<i<<" "<<j<<endl;
                    break;
                }
            }
        }
    }
    return 0;
}


#include <cstdio>
#include <algorithm>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	Bitset operator&(const Bitset &b) const
	{
		Bitset c(*this);
		c &= b;
		return c;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

inline Bitset operator-(const Bitset &a, const Bitset &b)
{
	Bitset c;
	for (int i = 0;i <= (N >> 6);++i)
		c.B[i] = a.B[i] - b.B[i];
	return c;
}

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q), Dis1[p].set(p), Dis1[q].set(q);
			}
	for (int i = 1;i <= N;++i)
		if (!leaf.qry(i))
		{
			int mn = 0X3F3F3F3F;
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					mn = min(mn, (Near[j] & leaf).popcount());
			for (int j = 1;j <= N;++j)
				if (leaf.qry(j) && (Near[j] & leaf).popcount() == mn)
					print(i), putc(' '), print(j), putc('\n');
		}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N];

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N], Leaf[Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		Leaf[i] = true;
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), Leaf[p] = Leaf[q] = false;
			}
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Dis1[j].set(i);
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (!Leaf[j] && !(Dis1[j] != Near[i]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline Bitset operator-(const Bitset &b) const
	{
		Bitset c;
		for (int i = 0;i <= (N >> 6);++i)
			c.B[i] = B[i] - b.B[i];
		return c;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q);
			}
	for (int i = 1;i <= N;++i)
		if (!leaf.qry(i))
			for (int j = 1;j <= N;++j)
				if (Near[j].have(Dis1[i]) && leaf.have(Near[j] - Dis1[j]))
				{
					for (int k = 1;k <= N;++k)
						if (leaf.qry(k) && Near[j].qry(k))
							print(i), putc(' '), print(k), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline Bitset operator-(const Bitset &b) const
	{
		Bitset c;
		for (int i = 0;i <= (N >> 6);++i)
			c.B[i] = B[i] - b.B[i];
		return c;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q);
			}
	for (int i = 1;i <= N;++i)
		if (!leaf.qry(i))
			for (int j = 1;j <= N;++j)
				if (Near[j].have(Dis1[i]) && leaf.have(Near[j] - Dis1[i]))
				{
					for (int k = 1;k <= N;++k)
						if (leaf.qry(k) && Near[j].qry(k))
							print(i), putc(' '), print(k), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>
#include <algorithm>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	Bitset operator&(const Bitset &b) const
	{
		Bitset c(*this);
		c &= b;
		return c;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], notleaf;

inline Bitset operator-(const Bitset &a, const Bitset &b)
{
	Bitset c;
	for (int i = 0;i <= (N >> 6);++i)
		c.B[i] = a.B[i] - b.B[i];
	return c;
}

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), notleaf.set(p), notleaf.set(q), Dis1[p].set(p), Dis1[q].set(q);
			}
	for (int i = 1;i <= N;++i)
		if (!notleaf.qry(i))
		{
			int Min = 0X3F3F3F3F;
			Bitset temp;
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Min = min(Min, (Near[j] & notleaf).popcount());
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i) && (Near[j] & notleaf).popcount() == Min)
					temp = (Near[j] & notleaf);
			for (int j = 1;j <= N;++j)
				if (temp.qry(j) && Dis1[j].popcount() == Min)
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
		}
	return 0;
}


#include <cstdio>
#include <algorithm>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	Bitset operator&(const Bitset &b) const
	{
		Bitset c(*this);
		c &= b;
		return c;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], notleaf;

inline Bitset operator-(const Bitset &a, const Bitset &b)
{
	Bitset c;
	for (int i = 0;i <= (N >> 6);++i)
		c.B[i] = a.B[i] - b.B[i];
	return c;
}

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), notleaf.set(p), notleaf.set(q), Dis1[p].set(p), Dis1[q].set(q);
			}
	for (int i = 1;i <= N;++i)
		if (!notleaf.qry(i))
		{
			int Min = 0X3F3F3F3F;
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Min = min(Min, (Near[j] & notleaf).popcount());
			for (int j = 1;j <= N;++j)
				if (Near[i].qry(j) && Dis1[j].popcount() == Min)
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
		}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset operator-(const Bitset &b) const
	{
		Bitset c;
		for (int i = 0;i <= (N >> 6);++i)
			c.B[i] = B[i] - b.B[i];
		return c;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q);
			}
	for (int i = 1;i <= N;++i)
		if (leaf.qry(i))
		{
			for (int j = 1;j <= N;++j)
				if (!leaf.qry(j) && Near[i].have(Dis1[j]) && leaf.have(Near[i] - Dis1[j]))
				{
					print(i), putc(' '), print(j), putc('\n');
					goto loop;
				}
			while (true)
				;
			loop :
			;
		}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[Max_N / 64 + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N];

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (Find.popcount() != 2)
		return false;
	for (int x = 0, u;x <= (N >> 6);++x)
		if (Find.B[x])
			for (ULL b = 0ULL;b <= 63ULL;++b)
				if (Find.B[x] & (1ULL << b))
				{
					u = (x << 6) + b;
					if (A == 0)
						A = u;
					else
						B = u;
				}
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

bool G[Max_N][Max_N], Leaf[Max_N];

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	for (int i = 1, k;i <= N;++i)
	{
		Leaf[i] = true;
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i + 1;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (G[p][q])
					continue;
				G[p][q] = G[q][p] = true, Dis1[p].set(q), Dis1[q].set(p);
				print(p), putc(' '), print(q), putc('\n'), Leaf[p] = Leaf[q] = false;
			}
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (Near[j].qry(i))
					Dis1[j].set(i);
	for (int i = 1;i <= N;++i)
		if (Leaf[i])
			for (int j = 1;j <= N;++j)
				if (!(Dis1[j] != Near[i]))
				{
					print(i), putc(' '), print(j), putc('\n');
					break;
				}
	return 0;
}


#include <cstdio>

using namespace std;
const int Max_N(1050);
typedef unsigned long long int ULL;

namespace io
{
	const int SIZE = (1 << 21) + 1;
	char ibuf[SIZE], *iS, *iT, obuf[SIZE], *oS = obuf, *oT = oS + SIZE - 1, c, qu[55]; int f, qr;
	#define gc() (iS == iT ? (iT = (iS = ibuf) + fread (ibuf, 1, SIZE, stdin), (iS == iT ? EOF : *iS ++)) : *iS ++)
	inline void flush (){fwrite (obuf, 1, oS - obuf, stdout);oS = obuf;}
	inline void putc (char x){*oS ++ = x;if (oS == oT) flush ();}
	template <class I>
	inline void gi (I &x) {for (f = 1, c = gc(); c < '0' || c > '9'; c = gc()) if (c == '-') f = -1;
		for (x = 0; c <= '9' && c >= '0'; c = gc()) x = x * 10 + (c & 15); x *= f;}
	template <class I>
	inline void print (I x){
		if (!x) putc ('0'); if (x < 0) putc ('-'), x = -x;while(x) qu[++ qr] = x % 10 + '0',  x /= 10;while (qr) putc (qu[qr--]);}
	struct Flusher_ {~Flusher_(){flush();}}io_flusher_;
}
using io::gi;
using io::putc;
using io::print;

int N, Cnt[1 << 16];

inline ULL cut(ULL S)
{
	return S & ((1ULL << 16ULL) - 1ULL);
}

inline int popcount(const ULL &S)
{
	return Cnt[cut(S)] + Cnt[cut(S >> 16ULL)] + Cnt[cut(S >> 32ULL)] + Cnt[cut(S >> 48ULL)];
}

struct Bitset
{
	ULL B[(Max_N >> 6) + 5];
	inline void set(int pos)
	{
		B[pos >> 6] |= (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline void del(int pos)
	{
		B[pos >> 6] &= (~0ULL - (1ULL << (static_cast<ULL>(pos) & 63ULL)));
	}
	inline bool qry(int pos) const
	{
		return B[pos >> 6] & (1ULL << (static_cast<ULL>(pos) & 63ULL));
	}
	inline int popcount() const
	{
		int Ret(0);
		for (int i = 0;i <= (N >> 6);++i)
			Ret += ::popcount(B[i]);
		return Ret;
	}
	inline bool have(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if ((B[i] & b.B[i]) != b.B[i])
				return false;
		return true;
	}
	Bitset& operator&=(const Bitset &b)
	{
		for (int i = 0;i <= (N >> 6);++i)
			B[i] &= b.B[i];
		return *this;
	}
	inline bool operator!=(const Bitset &b) const
	{
		for (int i = 0;i <= (N >> 6);++i)
			if (B[i] != b.B[i])
				return true;
		return false;
	}
};
Bitset Near[Max_N], Dis1[Max_N], leaf;

inline Bitset operator-(const Bitset &a, const Bitset &b)
{
	Bitset c;
	for (int i = 0;i <= (N >> 6);++i)
		c.B[i] = a.B[i] - b.B[i];
	return c;
}

bool notleaf_1()
{
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() < N)
			return false;
	for (int i = 2;i <= N;++i)
		print(1), putc(' '), print(i), putc('\n');
	return true;
}

bool notleaf_2()
{
	int Cnt = 0;
	for (int i = 1;i <= N;++i)
		if (Near[i].popcount() == N)
			++Cnt;
	if (Cnt != 2)
		return false;
	Bitset Find = Near[1];
	int A = 0, B = 0, goA = 0, goB = 0;
	for (int i = 1;i <= N;++i)
	{
		Find &= Near[i];
		if (Near[i].popcount() < N)
			if (goA == 0)
				goA = i;
			else
				if (Near[i] != Near[goA])
					if (goB == 0)
						goB = i;
					else
						if (Near[i] != Near[goB])
							return false;
	}
	if (goA == 0 || goB == 0 || Find.popcount() != 2)
		return false;
	for (int u = 1;u <= N;++u)
		if (Find.qry(u))
			if (A == 0)
				A = u;
			else
				B = u;
	print(A), putc(' '), print(B), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goA].qry(i))
			print(A), putc(' '), print(i), putc('\n');
	for (int i = 1;i <= N;++i)
		if (i != A && i != B && Near[goB].qry(i))
			print(B), putc(' '), print(i), putc('\n');
	return true;
}

int main()
{
	for (int S = 0;S < (1 << 16);++S)
		for (int i = 0;i < 16;++i)
			Cnt[S] += ((S & (1 << i)) != 0);
	gi(N);
	if (N == 2)
	{
		print(1), putc(' '), print(2), putc('\n');
		return 0;
	}
	for (int i = 1, k;i <= N;++i)
	{
		leaf.set(i);
		gi(k);
		for (int u;k--;)
			gi(u), Near[i].set(u);
	}
	if (notleaf_1())
		return 0;
	if (notleaf_2())
		return 0;
	Bitset temp;
	for (int i = 1;i <= N;++i)
		for (int j = i;j <= N;++j)
			if (((temp = Near[i]) &= Near[j]).popcount() == 2)
			{
				int p, q;
				p = q = 0;
				for (int x = 0, u;x <= (N >> 6);++x)
					if (temp.B[x])
						for (ULL b = 0ULL;b <= 63ULL;++b)
							if (temp.B[x] & (1ULL << b))
							{
								u = (x << 6) + b;
								if (p == 0)
									p = u;
								else
									q = u;
							}
				if (Dis1[p].qry(q))
					continue;
				Dis1[p].set(q), Dis1[q].set(p), print(p), putc(' '), print(q), putc('\n'), leaf.del(p), leaf.del(q), Dis1[p].set(p), Dis1[q].set(q);
			}
	for (int i = 1;i <= N;++i)
		if (!leaf.qry(i))
			for (int j = 1;j <= N;++j)
				if (Near[j].have(Dis1[i]) && leaf.have(Near[j] - Dis1[i]))
				{
					for (int k = 1;k <= N;++k)
						if (leaf.qry(k) && Near[j].qry(k))
							print(i), putc(' '), print(k), putc('\n');
					break;
				}
	return 0;
}