package main
import (
"bufio"
"fmt"
"os"
"sort"
"strconv"
)
type Reduction struct {
L []int
R []int
}
var n int
var reductions []Reduction
var final_edges [][2]int
func are_equal(s1, s2 []int) bool {
if len(s1) != len(s2) {
return false
}
for i, v := range s1 {
if v != s2[i] {
return false
}
}
return true
}
func backtrack(red_idx int, current_edges [][2]int) bool {
if red_idx < 0 {
final_edges = current_edges
return true
}
red := reductions[red_idx]
for _, p := range red.R {
N_p := make([]bool, n+1)
N_p[p] = true
count := 1
for _, e := range current_edges {
if e[0] == p {
if !N_p[e[1]] {
N_p[e[1]] = true
count++
}
} else if e[1] == p {
if !N_p[e[0]] {
N_p[e[0]] = true
count++
}
}
}
if count != len(red.R) {
continue
}
match := true
for _, v := range red.R {
if !N_p[v] {
match = false
break
}
}
if match {
new_edges := make([][2]int, len(current_edges), len(current_edges)+len(red.L))
copy(new_edges, current_edges)
for _, l := range red.L {
new_edges = append(new_edges, [2]int{l, p})
}
if backtrack(red_idx-1, new_edges) {
return true
}
}
}
return false
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords)
buf := make([]byte, 1024*1024)
scanner.Buffer(buf, 10*1024*1024)
if !scanner.Scan() {
return
}
n, _ = strconv.Atoi(scanner.Text())
active_sets := make([][]int, n)
for i := 0; i < n; i++ {
scanner.Scan()
k, _ := strconv.Atoi(scanner.Text())
set := make([]int, k)
for j := 0; j < k; j++ {
scanner.Scan()
set[j], _ = strconv.Atoi(scanner.Text())
}
sort.Ints(set)
active_sets[i] = set
}
var base_vertices []int
for len(active_sets) > 0 {
min_size := 1000000
var A []int
for _, set := range active_sets {
if len(set) < min_size {
min_size = len(set)
A = set
}
}
C := make([]int, n+1)
for _, set := range active_sets {
for _, v := range set {
C[v]++
}
}
k := len(A)
L := []int{}
R := []int{}
for _, v := range A {
if C[v] == k {
L = append(L, v)
} else {
R = append(R, v)
}
}
if len(R) == 0 {
base_vertices = A
break
}
reductions = append(reductions, Reduction{L: L, R: R})
new_active_sets := make([][]int, 0, len(active_sets))
removed_copies := 0
isL := make([]bool, n+1)
for _, v := range L {
isL[v] = true
}
for _, set := range active_sets {
if removed_copies < len(L) && are_equal(set, A) {
removed_copies++
continue
}
new_set := make([]int, 0, len(set))
for _, v := range set {
if !isL[v] {
new_set = append(new_set, v)
}
}
if len(new_set) > 0 {
new_active_sets = append(new_active_sets, new_set)
}
}
active_sets = new_active_sets
}
for _, center := range base_vertices {
initial_edges := make([][2]int, 0, len(base_vertices)-1)
for _, v := range base_vertices {
if v != center {
initial_edges = append(initial_edges, [2]int{v, center})
}
}
if backtrack(len(reductions)-1, initial_edges) {
break
}
}
out := bufio.NewWriter(os.Stdout)
for _, e := range final_edges {
fmt.Fprintf(out, "%d %d\n", e[0], e[1])
}
out.Flush()
}