Go: package main

/*
深さ優先探索
*/

import (
	"bufio"
	"fmt"
	"log"
	"os"
	"strconv"
	"strings"
)

func main() {
	scanner := makeScanner(8192)
	n := eGetInt(scanner)
	g := make([][]int, n+1)
	for i := 1; i < n+1; i++ {
		ints := eGetInts(scanner)
		num, vcount := ints[0], ints[1]
		g[i] = make([]int, vcount)
		for j := 0; j < vcount; j++ {
			g[num][j] = ints[j+2]
		}
	}
	hist := make([]struct{ d, f int }, n+1)
	t := 0
	var visit func(int)
	visit = func(num int) {
		if hist[num].d == 0 {
			stack := []int{}
			t++
			hist[num].d = t
			stack = append(stack, num)
			for len(stack) > 0 {
				// トップを訪問する
				top := stack[len(stack)-1]
				for i := 0; i < len(g[top]); i++ {
					if hist[g[top][i]].d == 0 {
						t++
						hist[g[top][i]].d = t
						stack = append(stack, g[top][i])
						goto endloop
					}
				}
				// 訪問場所がなくなったら
				// pop
				stack = stack[0 : len(stack)-1]
				t++
				hist[top].f = t
			endloop:
			}
		}
	}
	for i := 1; i < n+1; i++ {
		visit(i)
	}
	for i := 1; i < n+1; i++ {
		fmt.Println(i, hist[i].d, hist[i].f)
	}
}

////////////////////////////////////////////////////////////////////////////////
// ライブラリ
////////////////////////////////////////////////////////////////////////////////
func makeScanner(maxByte int) *bufio.Scanner {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Buffer(make([]uint8, 0, 8192), maxByte)
	return scanner
}
func makeWordScanner() *bufio.Scanner {
	scanner := makeScanner(8192)
	scanner.Split(bufio.ScanWords)
	return scanner
}
func eGetLine(r *bufio.Scanner) string {
	if r.Scan() {
		return r.Text()
	}
	err := r.Err()
	if err != nil {
		log.Fatal(err)
	}
	// EOF
	return ""
}
func eGetInt(r *bufio.Scanner) int {
	line := eGetLine(r)
	return eAtoi(line)
}
func eGetInt64(r *bufio.Scanner) int64 {
	line := eGetLine(r)
	return eAtoInt64(line)
}
func eGetFields(r *bufio.Scanner) []string {
	line := eGetLine(r)
	return strings.Fields(line)
}
func eGetInts(r *bufio.Scanner) []int {
	fields := eGetFields(r)
	ints := make([]int, len(fields))
	for i := 0; i < len(ints); i++ {
		ints[i] = eAtoi(fields[i])
	}
	return ints
}
func eGetInt64s(r *bufio.Scanner) []int64 {
	fields := eGetFields(r)
	ints := make([]int64, len(fields))
	for i := 0; i < len(ints); i++ {
		ints[i] = eAtoInt64(fields[i])
	}
	return ints
}
func eGetChars(r *bufio.Scanner) []string {
	line := eGetLine(r)
	return strings.Split(line, "")
}
func eAtoi(s string) int {
	n, err := strconv.Atoi(s)
	if err != nil {
		log.Fatal(err)
	}
	return n
}
func eAtoInt64(s string) int64 {
	n, err := strconv.ParseInt(s, 10, 64)
	if err != nil {
		log.Fatal(err)
	}
	return n
}

Python: n = int(input())
A = [[0 for i in range(n)] for j in range(n)]

for i in range(n):
    u, k, *v = list(map(int, input().split()))
    for j in v:
        A[int(u)-1][int(j)-1] = 1
        # A[int(j)-1][int(u)-1] = 1

for row in A:
    msg = ' '.join(map(str, row))
    # print(msg)

d = [-1] * n
f = [-1] * n

t = 0

# # recursive
# def dfs(u):
#     global t
#     # print('visit:', str(u), str(t))
#     t += 1
#     d[u] = t
#     for i in range(len(A[u])):
#         if A[u][i] == 1 and d[i] == -1:
#             dfs(i)
#     t += 1
#     f[u] = t
            

def get_next(u):
    for i in range(len(A[u])):
        if A[u][i] == 1 and d[i] == -1:
            return i
    return None

def dfs(u):
    global t
    s = []
    s.append(u)
    d[u] = t
    while len(s) > 0:
        p = s[-1]
        v = get_next(p)
        # print(p, 'next:', v)
        # t += 1
        # d[p] = t
        # print('poped:', p)
        
        if v is not None:
            
            if d[v] == -1:
                t += 1
                d[v] = t
                s.append(v)
                # print('[append:', v, 'at', t)
        else:
            t += 1
            # print('complete:', p, 'at', t)
            s.pop()
            f[p] = t


# def dfs(u):
#     s = []
#     t = 0
#     s.append(u)
    
#     while len(s) > 0:
#         p = s.pop()
#         print('init', str(p), d[p], t)
#         if d[p] > -1:
#             print('完了')
#             t += 1
#             f[p] = t
#         else:
#             t += 1
#             d[p] = t
#             print('---visit:', str(p), str(t), 'at', t)
#             has_node = False
#             for i in range(len(A[p])):
#                 if A[p][i] == 1 and d[i] == -1:
#                     print('reserve:', str(i))
#                     has_node = True
#                     s.append(i)
#                     break
#             if not has_node:
#                 t += 1
#                 f[p] = t
#         # print('H_complete:', str(p), str(t))
            


t = 0
for i in range(n):
    if d[i] == -1:
        t += 1
        # print('start from:', str(i))
        dfs(i)


for i in range(n):
    print(str(i+1), str(d[i]), str(f[i]))

Python: time=1
n=int(input())

A=[[] for i in range(n)]
d=[0 for i in range(n)]
f=[0 for i in range(n)]

for i in range(n):
	a=list(map(int,input().split()))
	for j in range(a[1]):
		A[i].append(a[2+j]-1)

def dfs(q,num):
	global d,f,time
	d[num]=time
	for nx in q[num]:
		if d[nx]==0:
			time+=1
			dfs(q,nx)
	time+=1
	f[num]=time
	
for i in range(n):
	if d[i]==0:#unvisit
		dfs(A,i)
		time+=1

for i in range(n):
	print(i+1,d[i],f[i])

Python: import sys

def push_vertex(frame, vtx, search_queue, outputs, vertices):
    #print("frame:{} push:{}".format(frame, vtx))
    outputs[vtx] = [frame, None]
    search_queue.append(vtx)
    [vertices[key].remove(vtx) for key in vertices.keys() if vtx in vertices[key]]

    return

def main():
    n = int(input())
    lines = sys.stdin.readlines()
    outputs = {}

    vertices = {}
    for i in range(n):
        idx, idx_n, *idx_vertices = lines[i].rstrip().split()
        vertices[int(idx)] = [int(x) for x in idx_vertices]

    vtx = 1
    frame = 1
    search_queue = []
    push_vertex(frame, vtx, search_queue, outputs, vertices)

    while len(vertices) != 0:
        frame += 1
        if len(search_queue) != 0:
            vtx = search_queue[-1]
        else:
            vtx = list(vertices.keys())[0]
            push_vertex(frame, vtx, search_queue, outputs, vertices)
            continue

        if len(vertices[vtx]) != 0:
            next_vtx = vertices[vtx][0]
            push_vertex(frame, next_vtx, search_queue, outputs, vertices)
        else:
            search_queue.pop()
            #print("frame:{},pop:{}".format(frame, vtx))
            del vertices[vtx]
            outputs[vtx][1] = frame

    #pprint.pprint(outputs)
    for key, value in sorted(outputs.items(), key=lambda x: x[0]):
        print("{} {} {}".format(key, value[0], value[1]))


main()

Python: N = int(input())
inf = 10 ** 6
graph = [[] for i in range(N)]
visited = [0 for i in range(N)]
time = [[inf,inf] for i in range(N)]

for i in range(N):
  a = list(map(int,input().split()))
  a = list(map(lambda x: x - 1,a))
  k,n = a[0],a[1]
  if n >= 0:
    graph[k] = a[2:]

con = 0

def dfs(cur,par):
  #print("v",visited)
  global con
  visited[cur] = 1
  con += 1
  time[cur][0] = min(time[cur][0],con)
  #print("cur1,con1",cur,con)
  if (graph[cur] == [] or graph[cur] == [cur]):
    visited[cur] = 1
    time[cur][0] = min(con,time[cur][0])
    con += 1
    time[cur][1] = min(con,time[cur][1])
    #print("curif,conif",cur,con)
    return 
  else:
    for chi in graph[cur]:
      if visited[chi] == 0:
        dfs(chi,cur)
      if any(visited[i] == 0 for i in graph[cur]):
        continue
      else:
        con += 1
        time[cur][1] = min(con,time[cur][1])
        #print("cur2,con2",cur,con)
        return
dfs(0,-1)

if any([i == 0 for i in visited]):
  f = visited.index(0)
  dfs(f,f - 1)

for i,t in enumerate(time):
  print(i + 1 ,*t)