Go: package main

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

//util
var sc = bufio.NewScanner(os.Stdin)

func nextStr() string {
	sc.Scan()
	return sc.Text()
}

func nextInt() int {
	sc.Scan()
	i, e := strconv.Atoi(sc.Text())
	if e != nil {
		panic(e)
	}
	return i
}

func printList(list []int) {
	n := len(list)
	buf := bufio.NewWriter(os.Stdout)
	for _, v := range list[0 : n-1] {
		buf.Write([]byte(fmt.Sprintf("%d ", v)))
	}
	buf.Write([]byte(fmt.Sprintf("%d\n", list[n-1])))
	buf.Flush()
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

func min(a, b int) int {
	if a > b {
		return b
	} else {
		return a
	}
}

//solve

const MAX = 100
const WHITE = 0
const GRAY = 1
const BLACK = 2

var time, n int

//隣接行列
var M [MAX][MAX]int

//d[i]: 最初にiを訪問した時刻。発見時刻
//f[i]: iの隣接リストを訪問し終えた時刻。完了時刻
var color, d, f [MAX]int

func dfs_visit(u int) {
	color[u] = GRAY
	time++
	d[u] = time
	//隣接を調べる
	for v := 0; v < n; v++ {
		if M[u][v] == 0 {
			continue
		}
		//隣接するvが、未訪問だった場合
		if color[v] == WHITE {
			dfs_visit(v)
		}
	}
	//訪問完了処理
	color[u] = BLACK
	time++
	f[u] = time
	//訪問完了
}

func dfs() {
	//初期化
	for u := 0; u < n; u++ {
		color[u] = WHITE
	}
	time = 0

	//未訪問のuを始点として、深さ優先探索
	for u := 0; u < n; u++ {

		if color[u] == WHITE {
			dfs_visit(u)
		}
	}

	for u := 0; u < n; u++ {
		fmt.Println(u+1, d[u], f[u])
	}
}

func main() {
	sc.Split(bufio.ScanWords)
	n = nextInt()
	for i := 0; i < n; i++ {
		u, k := nextInt(), nextInt()
		M[u-1][u-1] = 0

		//fmt.Println(u, k)
		if k > 0 {
			for j := 0; j < k; j++ {

				v := nextInt()
				M[u-1][v-1] = 1

			}
		}

	}

	dfs()

}

Python: n = int(input())
G = {i:[] for i in range(1, n+1)}
for _ in range(n):
    ukv = list(map(int, input().split()))
    u = ukv[0]
    k = ukv[1]
    for i in range(2, 2+k):
        G[u].append(ukv[i])
        
d = [-1 for _ in range(n+1)]
f = [-1 for _ in range(n+1)]
counter = 0

def dfs(n):
    global counter
    counter += 1
    d[n] = counter
    
    for n_i in G[n]:
        if d[n_i]==-1:
            dfs(n_i)
            
    counter += 1
    f[n] = counter

for i in range(1, n+1):
    if d[i]==-1:
        dfs(i)

for id_i, (d_i, f_i) in enumerate(zip(d[1:], f[1:]), 1):
    print(id_i, d_i, f_i)

Python: def dfs(G, u, t, d, visited):
  found = t
  visited.add(u)
  t += 1
  for v in G[u]:
    if v not in visited:
      t = dfs(G, v, t, d, visited)
  complete = t
  d[u] = (found, complete)
  t+=1
  return t


n = int(input())
G = {}
for _ in range(n):
  entries = list(map(int, input().split(' ')))
  u = entries[0]
  k = entries[1]
  G[u] = set()
  for v in entries[2:]:
    G[u].add(v)

d = {}
visited = set()
t = 1
for i in range(1,n+1):
  if i not in visited:
    t = dfs(G, i, t, d, visited)
for i in sorted(d):
  print(i,d[i][0],d[i][1])

Python: #!/usr/bin/env python
# -*- coding: utf-8 -*-

"""
input:
6
1 2 2 3
2 2 3 4
3 1 5
4 1 6
5 1 6
6 0

output:
1 1 12
2 2 11
3 3 8
4 9 10
5 4 7
6 5 6
"""
import sys

UNVISITED, VISITED_IN_STACK, POPPED_OUT = 0, 1, 2


def generate_adj_matrix(v_info):
    for v_detail in v_info:
        v_index, adj_num, *adj_list = map(int, v_detail)
        # assert len(adj_list) == adj_num
        init_adj_table[v_index].extend(sorted(adj_list, reverse=True))
    return init_adj_table


def graph_dfs(v_init):
    global time

    # init the first node of overall graph iterations(times >= 1)
    stack.append(v_init)
    color[v_init] = VISITED_IN_STACK
    d_time[v_init] += time
    # init end

    while stack:
        current = stack[-1]
        v_table = adj_table[current]
        color[current]=VISITED_IN_STACK
        # if adj is None, current's adj(s) have been all visited
        adj = v_table.pop() if v_table else None

        if adj:
            if color[adj] is UNVISITED:
                color[adj] = VISITED_IN_STACK
                time += 1
                d_time[adj] += time
                stack.append(adj)
        else:
            stack.pop()
            color[current] = POPPED_OUT
            time += 1
            f_time[current] += time

    return None


def dfs_start():
    global time
    for i in range(vertices):
        if color[i + 1] == UNVISITED:
            # print('round: ', i + 1)
            graph_dfs(i + 1)
            time += 1


if __name__ == '__main__':
    _input = sys.stdin.readlines()
    vertices = int(_input[0])
    v_info = map(lambda x: x.split(), _input[1:])
    init_adj_table = tuple([] for _ in range(vertices + 1))
    # assert len(vertex_info) == vertex_num

    # config length = (vertex_num + 1)
    stack = []
    color = [UNVISITED] * (vertices + 1)
    d_time, f_time = ([0] * (vertices + 1) for _ in range(2))

    adj_table = generate_adj_matrix(v_info)
    # timing start from 1
    time = 1
    dfs_start()

    for index, v in enumerate(zip(d_time[1:], f_time[1:]), 1):
        print(index, *v)

Python: #!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys

UNVISITED, VISITED_IN_STACK, POPPED_OUT = 0, 1, 2


def generate_adj_matrix(v_info):
    for v_detail in v_info:
        v_adj_list = v_detail[2:]
        # assert len(v_adj_list) == int(v_detail[1])
        for each in v_adj_list:
            init_adj_matrix[int(v_detail[0]) - 1][int(each) - 1] = 1
    return init_adj_matrix


def graph_dfs(v_init):
    global time

    # init the first node of overall graph iterations(times >= 1)
    vertices_visit_stack.append(v_init)
    vertices_status_list[v_init] = VISITED_IN_STACK
    vertices_d_time[v_init] += time
    # init end

    while len(vertices_visit_stack) > 1:
        current_vertex = vertices_visit_stack[-1]
        next_vertex = get_adj_vertices(vertex=current_vertex)

        if next_vertex:
            if vertices_status_list[next_vertex] is UNVISITED:
                vertices_status_list[next_vertex] = VISITED_IN_STACK
                time += 1
                vertices_d_time[next_vertex] += time
                vertices_visit_stack.append(next_vertex)
        else:
            vertices_visit_stack.pop()
            vertices_status_list[current_vertex] = POPPED_OUT
            time += 1
            vertices_f_time[current_vertex] += time

    return None


def get_adj_vertices(vertex):
    index = int(vertex) - 1
    for j in range(vertices_num):
        if adj_matrix[index][j]:
            adj_matrix[index][j] = 0
            return j + 1
    return None


def dfs_start():
    global time
    for i in range(vertices_num):
        if vertices_status_list[i + 1] == UNVISITED:
            # print('round: ', i + 1)
            graph_dfs(v_init=i + 1)
            time += 1


if __name__ == '__main__':
    _input = sys.stdin.readlines()
    vertices_num = int(_input[0])
    vertices_info = list(map(lambda x: x.split(), _input[1:]))
    init_adj_matrix = [[0] * vertices_num for _ in range(vertices_num)]
    # assert len(vertex_info) == vertex_num

    # config length = (vertex_num + 1)
    vertices_visit_stack = [-1]
    vertices_status_list = [UNVISITED] * vertices_num
    vertices_status_list.insert(0, -1)
    vertices_d_time, vertices_f_time = ([0] * (vertices_num + 1) for _ in range(2))

    adj_matrix = generate_adj_matrix(vertices_info)
    # timing start from 1
    time = 1
    dfs_start()
    for k in range(vertices_num):
        print(k + 1, vertices_d_time[k + 1], vertices_f_time[k + 1])

Python: n=int(input())
ne=[[] for _ in range(n+1)]
for _ in range(n):
    u,k,*v=map(int,input().split())
    ne[u]=v

class dfs:
    def __init__(self, n, ne):
        self.n=n
        self.ne=ne
        self.d=[0]*(n+1)
        self.f=[0]*(n+1)
        self.t=0
    def search(self, a):
        self.t+=1
        self.d[a]=self.t
        for p in ne[a]:
            if self.d[p]==0:
                self.search(p)
        self.t+=1
        self.f[a]=self.t
DFS=dfs(n,ne)
for i in range(1,n+1):
    if DFS.d[i]==0:
        DFS.search(i)
    print(i,DFS.d[i],DFS.f[i])