Go: package main

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

var scanner = bufio.NewScanner(os.Stdin)

func nextString() string {
	scanner.Scan()
	return scanner.Text()
}

func nextInt() int {
	n, err := strconv.Atoi(nextString())
	if err != nil {
		fmt.Printf("strconv.Atoi failed: %v\n", err)
	}
	return n
}

var infty = 1000000000

type nodeKey int

type DirectedGraphNode struct {
	key       nodeKey
	adjacents []*DirectedGraphNode
	visited   bool
	d         int
	f         int
	parent    *DirectedGraphNode
}

type DirectedGraph struct {
	nodes []*DirectedGraphNode
}

func (g *DirectedGraph) setAdjacency(l, r nodeKey) {
	g.nodes[l].adjacents = append(g.nodes[l].adjacents, g.nodes[r])
}

func newDirectedGraph(nNodes int) *DirectedGraph {
	graph := DirectedGraph{}
	graph.nodes = make([]*DirectedGraphNode, nNodes)
	for iNodes := 0; iNodes < nNodes; iNodes++ {
		graph.nodes[iNodes] = &DirectedGraphNode{}
		graph.nodes[iNodes].key = nodeKey(iNodes)

		graph.nodes[iNodes].visited = false
		graph.nodes[iNodes].d = infty
		graph.nodes[iNodes].f = infty
		graph.nodes[iNodes].parent = nil
	}
	return &graph
}

type QueueEntry struct {
	node *DirectedGraphNode
	next *QueueEntry
}

type Queue struct {
	head, tail *QueueEntry
}

func (q *Queue) enqueue(node *DirectedGraphNode) {
	entry := QueueEntry{}
	entry.node = node

	if q.tail == nil {
		q.head = &entry
		q.tail = &entry
		return
	}

	q.tail.next = &entry
	q.tail = q.tail.next
}

func (q *Queue) dequeue() *QueueEntry {
	if q.head == nil {
		return nil
	}
	poped := q.head
	q.head = q.head.next
	if q.head == nil {
		q.tail = nil
	}
	return poped
}

func (q *Queue) isEmpty() bool {
	return q.head == nil
}

var time = 0

func fillDistancesByDepthFirstSearch(g *DirectedGraph) {
	for _, u := range g.nodes[1:] {
		if !u.visited {
			dfsVisit(g, u)
		}
	}
}

func dfsVisit(g *DirectedGraph, u *DirectedGraphNode) {
	time++
	u.d = time

	u.visited = true
	for _, v := range u.adjacents {
		if !v.visited {
			v.parent = u
			dfsVisit(g, v)
		}
	}
	time++
	u.f = time
}

func main() {
	scanner.Split(bufio.ScanWords)

	nNodes := nextInt()
	g := newDirectedGraph(nNodes + 1)

	for iNode := 0; iNode < nNodes; iNode++ {
		nodeKeyLeft := nodeKey(nextInt())
		nAdj := nextInt()
		for iAdj := 0; iAdj < nAdj; iAdj++ {
			nodeKeyRight := nodeKey(nextInt())
			g.setAdjacency(nodeKeyLeft, nodeKeyRight)
		}
	}

	fillDistancesByDepthFirstSearch(g)

	for iNode := 1; iNode <= nNodes; iNode++ {
		node := g.nodes[iNode]
		fmt.Println(node.key, node.d, node.f)
	}
}

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

import sys

UNVISITED, VISITED_IN_STACK, POPPED_OUT = 0, 1, 2


def generate_adj(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)
    vertex_visit_stack.append(v_init)
    vertex_status_list[v_init] = VISITED_IN_STACK
    vertex_d_time[v_init] += time
    # init end
    while len(vertex_visit_stack) > 1:
        current_vertex = vertex_visit_stack[-1]
        next_vertex = get_next_vertex(node=current_vertex)
        # print(current_vertex, next_vertex, vertex_status_list, vertex_visit_stack)
        time += 1
        if next_vertex and vertex_status_list[next_vertex] is UNVISITED:
            # print('on', current_vertex, next_vertex)
            vertex_status_list[next_vertex] = VISITED_IN_STACK
            vertex_d_time[next_vertex] += time
            vertex_visit_stack.append(next_vertex)
        else:
            # other_vertex = get_next_vertex(current_vertex)
            # print('stuck', current_vertex, other_vertex)
            vertex_visit_stack.pop()
            vertex_status_list[current_vertex] = POPPED_OUT
            vertex_f_time[current_vertex] += time

    return None


def get_next_vertex(node):
    for j in range(vertex_num):
        if adj_matrix[int(node) - 1][j]:
            adj_matrix[int(node) - 1][j] = 0
            return j + 1


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


if __name__ == '__main__':
    _input = sys.stdin.readlines()
    vertex_num = int(_input[0])
    vertex_info = list(map(lambda x: x.split(), _input[1:]))
    init_adj_matrix = [[0] * vertex_num for _ in range(vertex_num)]
    # assert len(vertex_info) == vertex_num
    # config length -> (vertex_num + 1)
    vertex_visit_stack = [-1]
    vertex_status_list = [UNVISITED] * vertex_num
    vertex_status_list.insert(0, -1)
    vertex_d_time, vertex_f_time = ([0] * (vertex_num + 1) for _ in range(2))

    adj_matrix = generate_adj(vertex_info)

    time = 1
    dfs_start()

    for k in range(vertex_num):
        print(k + 1, vertex_d_time[k + 1], vertex_f_time[k + 1])

Python: from sys import stdin
input = stdin.readline

N = 100
WHITE = 0 # 未訪問
GRAY = 1 # 訪問したが、スタックに残っている
BLACK = 2 # 訪問完了済み

M = [[0]*N for _ in range(N)] # n*nの隣接行列の作成
color = [WHITE]*N  # colorの初期化
d = [0]*N # 最初の訪問時刻のタイムスタンプ
f = [0]*N # 完了時刻のタイムスタンプ

def dfs_visit(u): # uの訪問処理
    global tt
    color[u] = GRAY
    tt += 1
    d[u] = tt # 最初の訪問
    for v in range(n):
        if M[u][v] == 0:
            continue
        if color[v] == WHITE: # 深さがあれば再帰的に処理をする
            dfs_visit(v)
    color[u] = BLACK # uに関する全ての深さを探索しきった処理
    tt += 1
    f[u] = tt # 訪問の完了

def dfs():
    global tt
    tt = 0

    for u in range(n):
        if color[u] == WHITE: # 未訪問のuを始点にする
            dfs_visit(u)
    for u in range(n): # 最終的な表示処理
        print(str(u+1) + ' ' + str(d[u]) + ' ' + str(f[u]))

n = int(input())
for _ in range(n): # 隣接行列の作成
    u, k, *vv = input().split()
    u = int(u)-1  # 0オリジンにする
    k = int(k)    # kは使わない
    for v in vv:
        v = int(v)-1
        M[u][v] = 1
dfs()

Python: def dfs(v, k):
    if ans[v][1] == 0:
        ans[v][1] = k
    else:
        return k - 1
    for i in d[v]:
        k = dfs(i, k + 1)
    k += 1
    if ans[v][2] == 0:
        ans[v][2] = k
    return k


G = int(input())
vlst = [list(map(int, input().split())) for _ in range(G)]
d = [[] for _ in range(G)]
for i in range(G):
    for j in range(vlst[i][1]):
        d[i].append(vlst[i][2 + j] - 1)
ans = [[_, 0, 0] for _ in range(G)]
k = 1
for i in range(G):
    k = dfs(i, k) + 1
for i in ans:
    print(i[0] + 1, i[1], i[2])

Python: from collections import defaultdict


n = int(input())
rel = defaultdict(set)

times = {}
for _ in range(n):
    rl = list(map(int, input().split()))
    p, ts = rl[0], rl[2:]
    rel[p] = sorted(ts, reverse=True)
    times[p] = {'first': 0, 'last': 0}

t = 0

for f in range(1, len(rel)+1):
    if times[f]['last']:
        continue
    stack = [f]
    while stack:
        u = stack[-1]
        if times[u]['first']:
            if not times[u]['last']:
                t += 1
                times[u]['last'] = t
            stack.pop()
            continue
        t += 1
        times[u]['first'] = t
        for v in rel[u]:
            if not times[v]['first']:
                stack.append(v)

for k in sorted(times):
    print(k, times[k]['first'], times[k]['last'])

Python: import sys
sys.setrecursionlimit(10000000)
MOD = 10 ** 9 + 7
INF = 10 ** 15

G = [[] for _ in range(105)]
begin = [-1] * 105
end = [-1] * 105

t = 1
def dfs(v):
    global t
    begin[v] = t
    t += 1
    for e in G[v]:
        if begin[e] < 0:
            dfs(e)
    end[v] = t
    t += 1

def main():
    N = int(input())
    for i in range(N):
        A = list(map(int,input().split()))
        for a in A[2:]:
            G[i].append(a - 1)
    
    for i in range(N):
        if begin[i] < 0:
            dfs(i)
    for i in range(N):
        print(i + 1,begin[i],end[i])
if __name__ == '__main__':
    main()