Go: package main

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

/***************************************************************
stackによる深さ優先探索を実装する。
***************************************************************/

const (
	N     = 100
	WHITE = 0
	GRAY  = 1
	BLACK = 2
)

var (
	n            int
	Adj          [N][N]int
	statusColor  [N]int
	discoverTime [N]int
	finishTime   [N]int
	clock        int
	nt           [N]int
)

/***************************************************************
Stackの定義:
- field
	container: ノードの格納されたスライス
	sp: スタックポインタ
- method
	size(): スタックに積まれているノードの個数を返す
	top(): スタックの一番上に積まれているノードを返す
	isEmpty(): スタックが空かどうかを返す
	pop(): スタックの要素をポップし削除する
	push(): スタックに要素を積む
***************************************************************/

type Stack struct {
	container []int
	sp        int
}

func (s *Stack) size() int {
	return len(s.container)
}

func (s *Stack) top() int {
	return s.container[s.sp]
}

func (s *Stack) isEmpty() bool {
	return s.sp == 0
}

func (s *Stack) pop() int {
	_pop := func() {
		s.container = s.container[:len(s.container)-1]
	}
	defer _pop()
	if s.isEmpty() {
		panic("There is no elements in stack!")
	}
	s.sp--
	return s.container[s.sp]
}

func (s *Stack) push(n int) {
	s.sp++
	s.container[s.sp] = n
}

func next(u int) int {
	for v := nt[u]; v < n; v++ {
		nt[u] = v + 1
		if Adj[u][v] == 1 {
			return v
		}
	}
	return -1
}

func _DFS(target int) {
	for i := 0; i < n; i++ {
		nt[i] = 0
	}
	stack := new(Stack)
	stack.container = make([]int, N+1)
	stack.push(target)
	statusColor[target] = GRAY
	clock++
	discoverTime[target] = clock
	for !stack.isEmpty() {
		u := stack.top()
		v := next(u)
		if v != -1 {
			if statusColor[v] == WHITE {
				statusColor[v] = GRAY
				clock++
				discoverTime[v] = clock
				stack.push(v)
			}
		} else {
			stack.pop()
			statusColor[u] = BLACK
			clock++
			finishTime[u] = clock
		}
	}
}

func DFS() {
	for i := 0; i < n; i++ {
		statusColor[i] = WHITE
		nt[i] = 0
	}
	clock = 0
	for i := 0; i < n; i++ {
		if statusColor[i] == WHITE {
			_DFS(i)
		}
	}
	for i := 0; i < n; i++ {
		fmt.Printf("%d %d %d\n", i+1, discoverTime[i], finishTime[i])
	}
}

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	scanner.Split(bufio.ScanWords)
	scanner.Scan()
	n, _ = strconv.Atoi(scanner.Text())
	for i := 0; i < n; i++ {
		for j := 0; j < n; j++ {
			Adj[i][j] = 0
		}
	}
	for i := 0; i < n; i++ {
		scanner.Scan()
		u, _ := strconv.Atoi(scanner.Text())
		scanner.Scan()
		k, _ := strconv.Atoi(scanner.Text())
		u--
		for j := 0; j < k; j++ {
			scanner.Scan()
			v, _ := strconv.Atoi(scanner.Text())
			v--
			Adj[u][v] = 1
		}
	}

	DFS()
}

C++: #include<bits/stdc++.h>
using namespace std;

#define White 0 //未訪問
#define Gray 1 //訪問中、または未訪問へのEdgeがある
#define Black 2 //訪問済み

int n;
int Graph[100][100] = {};

int color[100] = {}, d[100], f[100], tt; 
// d[v] = v 訪問時の時間 f[v] = v の隣接リスト調査後の時間 tt = 時間の記録

int nt[100] = {};


//u に隣接する v を番号順に取得
int next(int u){
  for(int v = nt[u] ; v < n ; v++ ){
    nt[u] = v + 1;
    if(Graph[u][v] != 0) return v;
  }
  return -1;
}

void dfs_visit(int r){
  stack<int> S;
  S.push(r); //スタート地点をスタックに積む

  color[r] = Gray; //スタート地点を訪問済みに

  tt++;
  d[r] = tt; //時間を取得

  while(!S.empty()){
    int u = S.top();  //topの値を入れる
    int v = next(u);
    if(v != -1){
      //末訪問なら訪問中にし時間を取得、次の頂点をスタックに積む
      //どちらでもなければ、popでスタックから外し訪問済みにする
      if(color[v] == White){ 
	color[v] = Gray;
	tt++;
	d[v] = tt;
	S.push(v);
      }
    }else{
      S.pop();
      color[u] = Black;
      tt++;
      f[u] = tt;
    }
  }
}
  
void dfs(){
  tt = 0;

  for(int u = 0 ; u < n ; u++){
    if(color[u] == White) dfs_visit(u); //その頂点が未訪問なら
  }

  for(int i = 0 ; i < n ; i++){
    cout << i + 1 << " " << d[i] << " " << f[i] << endl;
  }
}

int main(){
  int u, k, v; //u = 頂点番号 k = 出次数 v = u に隣接する頂点番号

  cin >> n;
     
  for(int i = 0 ; i < n ; i++){
    cin >> u >> k; 
    u--;
    for(int j = 0 ; j < k ; j++){
      cin >> v; 
      v--;
      Graph[u][v] = 1;
    }
  }

  dfs();
     
  return 0;
}

C++: #define _USE_MATH_DEFINES
#include<iostream>
#include<algorithm>
#include<string>
#include<iomanip>
#include<cmath>
#include<vector>
#include<deque>
#include<queue>
#include<map>
#include<set>
#define ll long long
#define vec vector
#define vll vec<ll>
#define dou(s) atof(s.c_str())
#define lon(s) atoll(s.c_str())
#define str(n) to_string(n)
#define rep(i,a,b) for(ll i=a;i<b;i++)
#define rrep(i,b,a) for(ll i=b;i>=a;i--)
#define ALL(a) a.begin(),a.end()
#define rALL(a) a.rbegin(),a.rend()
#define line "\n"
#define mav(a) (*max_element(ALL(a)))
#define miv(a) (*min_element(ALL(a)))
#define vfind(a,n) (find(ALL(a),n)-a.begin())
#define prque priority_queue
#define MOD 1000000007ll
using namespace std;
void solve();
int main() {
	cin.tie(0);
	ios::sync_with_stdio(false);
	solve();
}

//nCk(mod p)
/*ll fac[510000], finv[510000], inv[510000];
void cmodf(ll p = MOD) {
	fac[0] = fac[1] = 1;
	finv[0] = finv[1] = 1;
	inv[1] = 1;
	rep(i, 2, 510000) {
		fac[i] = fac[i - 1] * i%p;
		inv[i] = p - inv[p%i] * (p / i) % p;
		finv[i] = finv[i - 1] * inv[i] % p;
	}
}
ll cmod(ll n, ll k, ll p = MOD) {
	return fac[n] * (finv[k] * finv[n - k] % p) % p;
}*/

//最大公約数
ll gcd(ll n, ll m) {
	ll k = n % m;
	while (k != 0) { n = m; m = k; k = n % m; }
	return m;
}
ll gcd(vector<ll> a) {
	rep(i, 1, a.size()) {
		ll k = a[i - 1] % a[i];
		while (k != 0) {
			a[i - 1] = a[i];
			a[i] = k;
			k = a[i - 1] % a[i];
		}
	}
	return a.back();
}

//最小公倍数
ll lcm(ll n, ll m) {
	ll t = n * m, k = n % m;
	while (k != 0) { n = m; m = k; k = n % m; }
	return t / m;
}
ll lcm(vector<ll> a) {
	ll n;
	rep(i, 1, a.size()) {
		n = a[i - 1] * a[i];
		ll k = a[i - 1] % a[i];
		while (k != 0) {
			a[i - 1] = a[i];
			a[i] = k;
			k = a[i - 1] % a[i];
		}
		n /= a[i];
		a[i] = n;
	}
	return n;
}

//keyの小さい方から探していった時に初めてvalueがjoken以上になる時のkey（keyが増えたらvalueも増えるか同じになるとき）
ll value(ll key) {
	/*変更*/
	return 0;
}
ll mini(ll lkey, ll rkey/*ここも調べる*/, ll joken) {
	ll mkey;
	while (lkey != rkey) {
		mkey = (lkey + rkey) / 2;
		if (value(mkey) >= joken)rkey = mkey;
		else lkey = mkey + 1;
	}
	return rkey;
}

//ペア配列の整理
struct cpl {
	ll f, s;
	bool operator<(const cpl &c) const {
		return f != c.f ? f < c.f : s < c.s;
	};
};

//変数定義

//再帰関数

//アルゴリズム
void solve() {
	ll n; cin >> n;
	vec<vll> a(n, vll(n, 0));
	rep(i, 0, n) {
		ll k, m; cin >> k >> m;
		rep(j, 0, m) {
			ll l; cin >> l;
			a[k - 1][l - 1] = 1;
		}
	}
	vll st(n, 0), en(n, 0);
	ll t = 0;
	while (miv(st) == 0) {
		ll k; t++;
		rep(i, 0, n)if (st[i] == 0) { k = i; break; }
		deque<ll> b(1, k);
		st[k] = t;
		rep(i, 0, n)a[i][k] = 0;
		while (b.size() != 0) {
			t++;
			bool x = true;
			rep(i, 0, n) {
				if (a[b[b.size() - 1]][i] == 1) {
					rep(j, 0, n)a[j][i] = 0;
					b.push_back(i);
					st[i] = t;
					x = false;
					break;
				}
			}
			if (x) {
				en[b[b.size() - 1]] = t;
				b.pop_back();
			}
		}
	}
	rep(i, 0, n) {
		cout << i + 1 << " " << st[i] << " " << en[i] << line;
	}
}

C++: #include<math.h>
#include<cmath>
#include<bitset>
#include<stdio.h>
#include<string>
#include<map>
#include<algorithm>
#include<vector>
#include<iostream>
#include<utility>
using namespace std;
vector<int> seen;
int d[110],f[110],z=1;
int dfs(vector<vector<int> > g,int v){
  int i;
  seen[v]=1;
  d[v]=z;
  z++;
  for(i=0;i<g[v].size();i++){
    if(seen[g[v][i]]==0){
      dfs(g,g[v][i]);
    }
  }
  f[v]=z;
  z++;
};
int main(){
  vector<vector<int> > g(110);
  int x,i,j,u,k,n,v;
  scanf("%d\n",&n);
  for(i=0;i<n;i++){
    scanf("%d %d",&u,&k);
    u--;
    for(j=0;j<k;j++){
      scanf("%d",&v);
      v--;
      g[u].push_back(v);
    }
  }
  seen.assign(n,0);
  for(i=0;i<n;i++){
    if(seen[i]==0){
      dfs(g,i);
    }
  }
  for(i=0;i<n;i++){
    printf("%d %d %d\n",i+1,d[i],f[i]);
  }
  return 0;
}