using System;
using System.Collections.Generic;
using System.Linq;
// Q037 深さ優先探索 https://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=ALDS1_11_B&lang=jp
class Program
{
static string InputPattern = "InputX";
static List<string> GetInputList()
{
var WillReturn = new List<string>();
if (InputPattern == "Input1") {
WillReturn.Add("4");
WillReturn.Add("1 1 2");
WillReturn.Add("2 1 4");
WillReturn.Add("3 0");
WillReturn.Add("4 1 3");
//1 1 8
//2 2 7
//3 4 5
//4 3 6
}
else if (InputPattern == "Input2") {
WillReturn.Add("6");
WillReturn.Add("1 2 2 3");
WillReturn.Add("2 2 3 4");
WillReturn.Add("3 1 5");
WillReturn.Add("4 1 6");
WillReturn.Add("5 1 6");
WillReturn.Add("6 0");
//1 1 12
//2 2 11
//3 3 8
//4 9 10
//5 4 7
//6 5 6
}
else {
string wkStr;
while ((wkStr = Console.ReadLine()) != null) WillReturn.Add(wkStr);
}
return WillReturn;
}
struct JyoutaiDef
{
internal int CurrNode;
}
// 隣接リスト
static Dictionary<int, int[]> mToNodeArrDict = new Dictionary<int, int[]>();
static void Main()
{
List<string> InputList = GetInputList();
int N = int.Parse(InputList[0]);
int[] wkArr = { };
Action<string> SplitAct = pStr =>
wkArr = pStr.Split(' ').Select(X => int.Parse(X)).ToArray();
foreach (string EachStr in InputList.Skip(1)) {
SplitAct(EachStr);
int NodeID = wkArr[0];
int[] ToNodeArr = EachStr.Split(' ').Skip(2).Select(pX => int.Parse(pX)).ToArray();
mToNodeArrDict[NodeID] = ToNodeArr;
}
for (int I = 1; I <= N; I++) {
if (mFindTimeDict.ContainsKey(I) == false) {
ExecDFS(I);
}
}
for (int I = 1; I <= N; I++) {
Console.WriteLine("{0} {1} {2}", I, mFindTimeDict[I], mEndTimeDict[I]);
}
}
// 現在時間
static int mCurrTime = 1;
// 最初に訪問した、発見時刻のDict
static Dictionary<int, int> mFindTimeDict = new Dictionary<int, int>();
// 隣接リストを調べ終えた、完了時刻のDict
static Dictionary<int, int> mEndTimeDict = new Dictionary<int, int>();
// 深さ優先探索を行う
static void ExecDFS(int pStartNode)
{
var Stk = new Stack<JyoutaiDef>();
JyoutaiDef WillPush;
WillPush.CurrNode = pStartNode;
Stk.Push(WillPush);
mFindTimeDict[pStartNode] = mCurrTime++;
while (Stk.Count > 0) {
JyoutaiDef Peeked = Stk.Peek(); //PopでなくPeekを使う
bool Pushed = false;
foreach (int EachToNode in mToNodeArrDict[Peeked.CurrNode].OrderBy(pX => pX)) {
if (mFindTimeDict.ContainsKey(EachToNode)) continue;
mFindTimeDict[EachToNode] = mCurrTime++;
WillPush.CurrNode = EachToNode;
Stk.Push(WillPush);
Pushed = true;
break; // 子ノードを1つPushしたらBreak
}
if (Pushed == false) {
mEndTimeDict[Peeked.CurrNode] = mCurrTime++;
Stk.Pop(); // スタックのTopを消しておく
}
}
}
}