Go: package main

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

func main() {
	sc := bufio.NewScanner(os.Stdin)
	sc.Split(bufio.ScanWords)
	sc.Scan()
	num, _ := strconv.Atoi(sc.Text())
	nodes := make(map[int]*node)

	for i := 0; i < num; i++ {
		sc.Scan()
		id, _ := strconv.Atoi(sc.Text())
		sc.Scan()
		nodeNum, _ := strconv.Atoi(sc.Text())
		n := node{id, make([]int, 0), 0, 0}
		for j := 0; j < nodeNum; j++ {
			sc.Scan()
			nid, _ := strconv.Atoi(sc.Text())
			n.nexts = append(n.nexts, nid)
		}
		nodes[id] = &n
	}

	s := Stack{}
	for i := 1; i < len(nodes)+1; i++ {
		if nodes[i].s != 0 {
			continue
		}
		walk(i, &nodes, s)
	}
	for i := 1; i < len(nodes)+1; i++ {
		fmt.Printf("%v %v %v\n", (*nodes[i]).id, (*nodes[i]).s, (*nodes[i]).e)
	}
}

var time int

func walk(nid int, nodes *map[int]*node, s Stack) {
	n := (*nodes)[nid]
	if n.s != 0 {
		return
	}
	time++
	n.s = time
	s.Push(n)
	for _, v := range n.nexts {
		walk(v, nodes, s)
	}
	time++
	n.e = time
}

type node struct {
	id    int
	nexts []int
	s     int
	e     int
}

// Stack is Stack
type Stack struct {
	stack []interface{}
}

// Push new value
func (stack *Stack) Push(val interface{}) {
	stack.stack = append(stack.stack, val)
}

// Pop new value
func (stack *Stack) Pop() interface{} {
	val := stack.stack[len(stack.stack)-1]
	stack.stack = stack.stack[0 : len(stack.stack)-1]
	return val
}

C++: #include<iostream>
#include<stack>
using namespace std;
#define N 101
#define gray 1
#define white 0
#define black 2
int n,m[N][N];
int color[N],d[N],f[N],tt;
int nt[N];
int next(int u)
{
	for(int v=nt[u];v<n;v++)
	{
		nt[u]=v+1;
		if(m[u][v]) return v;
	}
	return -1;
}
void dfs_v(int u)
{
	for(int i=1;i<=n;i++)
	{
		nt[i]=0;
	}
	stack<int> s;
	s.push(u);
	d[u]=++tt;
	color[u]=gray;
	while(!s.empty())
	{
		int x=s.top();
		int v=next(x);
		if(v!=-1)
		{
			if(color[v]==white)
			{
				color[v]=gray;
				d[v]=++tt;
				s.push(v);
			}
		}else
			{
				s.pop();
				color[x]=black;
				f[x]=++tt;
			}
	}
}
void dfs()
{
	for(int i=1;i<=n;i++)
	{
		color[i]=white;
		nt[i]=0;
	}
	tt=0;
	for(int i=1;i<=n;i++)
	{
		if(color[i]==white)
		{
			dfs_v(i);
		}
	}
	for(int i=1;i<=n;i++)
	{
		printf("%d %d %d\n",i,d[i],f[i]);
	}
}
int main()
{
	int v,k,u;
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=n;j++)
		{
			m[i][j]=0;
		}
	}
	for(int i=1;i<=n;i++)
	{
		cin>>u>>k;
		for(int j=1;j<=k;j++)
		{
			cin>>v;
			m[u][v]=1;
		}
	}
	dfs();
	return 0;
}

C++: #include <iostream>
using namespace std;

static const int N = 100;
static const int WHITE = 0;
static const int GRAY = 1;
static const int BLACK = 2;

int M[N][N] = {0}, color[N] = {0}, d[N], f[N], tt = 0, n;

void dfs_visit(int u) {
  int v;
  color[u] = GRAY;
  d[u] = ++tt;
  for (v = 0; v < n; v++) {
    if (M[u][v] == 0) continue;
    if (color[v] == WHITE) {
      dfs_visit(v);
    }
  }
  color[u] = BLACK;
  f[u] = ++tt;
}

int main(void) {
  int u, v, k, i, j;

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

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

  return 0;
}

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;
	}
}