using Extention;
// optional
using Graph;
using IOUtil;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
public static class Program {
    private static int[][] matrix;
    static int[] d, f;
    private static Color[] colors;
    static int t;


    public static void Main() {
        #region SetAutoFlushIsFalse
#if !DEBUG
        var sw = new StreamWriter(Console.OpenStandardOutput()) { AutoFlush = false };
        Console.SetOut(sw);
#endif
        #endregion

        var n = ReadLib.ReadAsInt();
        var graph = new SimpleAdjacencyMatrix(n);

        for (int i = 1; i <= n; i++) {
            var arr = ReadLib.ReadAsIntArray();
            graph.AddEdgeVal(i, arr[1], arr.Skip(2).ToArray());
        }

        matrix = graph.Matrix;
        d = new int[n + 1];
        f = new int[n + 1];
        t = 1;
        colors = Enumerable.Repeat(Color.White, n + 1).ToArray();

        for (int i = 1; i < n; i++) {
            if (colors[i] == Color.White) {
                dfs(i);
            }
        }

        for (int i = 1; i <= n; i++) {
            Console.WriteLine($"{i} {d[i]} {f[i]}");
        }
        #region Flush
#if !DEBUG
        Console.Out.Flush();
        sw.Close();
#endif
        #endregion
    }

    private static void dfs(int id) {
        colors[id] = Color.Gray;
        d[id] = t++;

        for (int i = 1; i < matrix.Length; i++) {
            if (colors[i] == Color.White && matrix[id][i] == 1) {
                dfs(i);
            }
        }

        colors[id] = Color.Black;
        f[id] = t++;
    }
}

namespace IOUtil {
    static class ReadLib {
        public static long ReadAsLong() {
            return long.Parse(Console.ReadLine());
        }

        public static int ReadAsInt() {
            return int.Parse(Console.ReadLine());
        }

        public static int[] ReadAsIntArray(int step = 0) {
            return Console.ReadLine().Split(' ').Select(s => int.Parse(s) + step).ToArray();
        }

        public static long[] ReadAsLongArray(int step = 0) {
            return Console.ReadLine().Split(' ').Select(s => long.Parse(s) + step).ToArray();
        }

        public static ulong[] ReadAsULongArray() {
            return Console.ReadLine().Split(' ').Select(s => ulong.Parse(s)).ToArray();
        }

        public static string[] ReadAsStringArray() {
            return Console.ReadLine().Split(' ');
        }

        public static int[] ReadNTimesAsInt(int n, int step = 0) {
            return Enumerable.Range(1, n).Select(s => int.Parse(Console.ReadLine()) + step).ToArray();
        }

        public static char[][] ReadNTimesAsCharArray(int n) {
            return Enumerable.Range(1, n).Select(i => Console.ReadLine().ToArray()).ToArray();
        }

        public static long[] ReadNTimesAsLong(int n, int step = 0) {
            return Enumerable.Range(1, n).Select(s => long.Parse(Console.ReadLine()) + step).ToArray();
        }

        public static int[][] ReadNTimesAsIntArray(int n, int step = 0) {
            return Enumerable.Range(1, n).Select(s => ReadAsIntArray(step)).ToArray();
        }

        public static string[] ReadNTimesAsString(int n) {
            return Enumerable.Range(1, n).Select(s => Console.ReadLine()).ToArray();
        }
    }
}

namespace Heaps {
    /// <summary>優先度付きキュー</summary>
    public class PriorityQueue<T> : IEnumerable<T>, IReadOnlyCollection<T> where T : IComparable<T> {
        public List<T> HeapArray { get; }
        public T NIL { get; }

        public int Count => HeapArray.Count;

        /// <param name="heapArray"></param>
        /// <param name="NIL"></param>
        public PriorityQueue(IEnumerable<T> heapArray, T NIL) {
            this.HeapArray = heapArray.ToList();
            this.NIL = NIL;
            BuildMaxHeap();
        }

        public PriorityQueue(T NIL) {
            this.HeapArray = new List<T>();
            this.NIL = NIL;
        }

        /// <summary>idのKeyを取得</summary>
        public T GetValue(int key) {
            if (key <= 0 || key > HeapArray.Count) {
                return NIL;
            }

            return HeapArray[key - 1];
        }

        /// <summary>parentKey [ id / 2 ]</summary>
        public int GetParentKey(int key) {
            return key / 2;
        }

        public int GetLeftKey(int key) {
            return 2 * key;
        }

        public int GetRightKey(int key) {
            return 2 * key + 1;
        }

        /// <summary>max Heap木とする O(N)</summary>
        public void BuildMaxHeap() {
            var n = HeapArray.Count() / 2;
            /// <summary>葉から順に処理を行う</summary>
            for (int i = n; i >= 1; i--) {
                MaxHeapify(i);
            }
        }

        /// <summary>idを根とする部分木について、maxHeap木を維持したまま、idを下降させる</summary>
        private void MaxHeapify(int key) {
            var leftKey = GetLeftKey(key);
            var rightKey = GetRightKey(key);

            var left = GetValue(leftKey);
            var right = GetValue(rightKey);
            var val = GetValue(key);

            int largestKey = key;
            T largestValue = val;

            if (left.CompareTo(NIL) != 0 && left.CompareTo(val) > 0) {
                largestKey = leftKey;
                largestValue = left;
            }

            if (right.CompareTo(NIL) != 0 && right.CompareTo(largestValue) > 0) {
                largestKey = rightKey;
            }

            if (largestKey != key) {
                HeapArray.Swap(key - 1, largestKey - 1);
                MaxHeapify(largestKey); // => 元idのノード
            }
        }

        /// <summary>keyの値を挿入する</summary>
        public void Enqueue(T value) {
            HeapArray.Add(value);

            var insertedKey = HeapArray.Count;
            var parentKey = GetParentKey(insertedKey);

            // 挿入Nodeが根になる or 親 > 子になるまで親とNodeを交換
            while (insertedKey > 1 && value.CompareTo(GetValue(parentKey)) > 0) {
                HeapArray.Swap(insertedKey - 1, parentKey - 1);
                insertedKey = parentKey;
                parentKey = GetParentKey(insertedKey);
            }
        }

        public T Dequeue() {
            if (HeapArray.Count == 0) {
                return NIL;
            }

            var popVal = HeapArray.First();
            var newRootVal = HeapArray.Last();  // 新しい根となる。

            HeapArray.RemoveAt(HeapArray.Count - 1);    // del last

            if (HeapArray.Count > 0) {
                HeapArray[0] = newRootVal;
                MaxHeapify(1);
            }

            return popVal;
        }

        public T Peek() {
            return HeapArray.First();
        }


        public IEnumerator<T> GetEnumerator() {
            while (HeapArray.Count > 0) {
                yield return Dequeue();
            }
        }

        IEnumerator IEnumerable.GetEnumerator() {
            return GetEnumerator();
        }

        public void Clear() {
            HeapArray.Clear();
        }

        public bool Contains(T val) {
            return HeapArray.Contains(val);
        }

        public PriorityQueue<T> Clone() {
            return new PriorityQueue<T>(HeapArray.DeepCopy(), NIL);
        }
    }
}

namespace Extention {
    /// <summary>拡張メソッドs</summary>
    static class MyExtention {
        /// <summary>iとjを入れ替える</summary>
        public static void Swap<T>(this T[] arr, int i, int j) {
            var buf = arr[i];
            arr[i] = arr[j];
            arr[j] = buf;
        }

        public static void Swap<T>(this List<T> list, int i, int j) {
            var buf = list[i];
            list[i] = list[j];
            list[j] = buf;
        }

        /// <summary>
        /// partition。末尾sArr[r]を基準として、左に基準より小さい数、右に基準より大きい数が存在するように配列を並び替える。
        /// </summary>
        /// <param name="sArr"></param>
        /// <param name="p">partitionの先頭の添字</param>
        /// <param name="r">partitionの末尾の添字</param>
        /// <returns>partitionの基準が存在する添字</returns>
        public static int Partition(this int[] sArr, int p, int r) {
            var x = sArr[r];
            var i = p - 1;
            for (int j = p; j < r; j++) {
                if (sArr[j] <= x) {
                    i++;
                    sArr.Swap(i, j);
                }
            }
            sArr.Swap(i + 1, r);
            return i + 1;
        }

        /// <summary>深いコピーをする</summary>
        public static T DeepCopy<T>(this T obj) {
            T result;
            var b = new BinaryFormatter();
            using (var mem = new MemoryStream()) {
                b.Serialize(mem, obj); //  binaryFormatterによりobjをmemにserial化して書き出す
                mem.Position = 0; // memのpotisionを先頭に戻す
                result = (T)b.Deserialize(mem); // memの内容を読み取る。
            }
            return result;
        }

        public static string Left(this string str, int length) {
            return str.Substring(0, length);
        }

        public static string Right(this string str, int length) {
            return str.Substring(str.Length - length, length);
        }

        public static int Find<T>(this T[] array, T obj) {
            return Array.IndexOf(array, obj);
        }

        public static int LastFind<T>(this T[] array, T obj) {
            return Array.LastIndexOf(array, obj);
        }
    }
}

static class MyMath {
    /// <summary>階乗</summary>
    public static long Factorial(int n) {
        if (n == 0) {
            return 1;
        }

        return Permutation(n, 0);
    }

    public static long Permutation(int n, int k) {
        return Enumerable.Range(0, n - k).Select(i => n - i).Aggregate(1, (b, i) => b * i);
    }

    #region FullPower
    /// <summary>総乗、π</summary>
    public static long FullPower(IEnumerable<long> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }

    public static long FullPower(IEnumerable<int> vs) {
        return vs.Aggregate(1L, (a, b) => a * b);
    }
    #endregion

    /// <summary>最大公約数</summary>
    public static long Gcd(long item1, long item2) {
        var a = Math.Max(item1, item2);
        var b = Math.Min(item1, item2);

        while (true) {
            long mod;
            Math.DivRem(a, b, out mod);
            if (mod == 0) {
                break;
            }

            a = b;
            b = mod;
        }
        return b;
    }

    /// <summary>約分。最大公約数で割ったものを返す。</summary>
    public static Tuple<long, long> DeviedByGcd(long item1, long item2) {
        var gcd = Gcd(item1, item2);
        return new Tuple<long, long>(item1 / gcd, item2 / gcd);
    }

    public static long Lcm(int item1, int item2) {
        return Math.BigMul(item1, item2) / Gcd(item1, item2);
    }

    public static ModCombination ModCombination(int maxN, int mod = 1000000007) {
        return new ModCombination(maxN, mod);
    }

    /// <summary>a^n mod</summary>
    public static int ModPow(long a, long n, int mod = 1000000007) {
        long res = 1;
        long pow = a;

        while (n > 0) {
            if ((n & 1) == 1) {
                res = (pow * res) % mod;    // resにaをかける
            }

            pow = (pow * pow) % mod;    // 次のaはa^2
            n >>= 1;
        }

        return (int)res;
    }
}

static class MyMethod {
    /// <summary>数え上げて、key, 出現回数に変換</summary>
    public static Dictionary<T, int> CountUp<T>(IEnumerable<T> str) {
        return
            str
            .GroupBy(c => c)
            .Select(g => new KeyValuePair<T, int>(g.Key, g.Count()))
            .ToDictionary(e => e.Key, e => e.Value);
    }

    /// <summary>ある要素を持つindexをすべて返す</summary>
    public static int[] InducesOfValue<T>(this T[] arr, T searchValue) {
        return arr
            .Select((v, i) => new { v, i })
            .Where(o => searchValue.Equals(o.v))
            .Select(o => o.i)
            .ToArray();
    }

    /// <summary>ある要素を持つindexをすべて返す</summary>
    public static int[] Induces<T>(this IEnumerable<T> arr, T searchValue) {
        return arr.ToArray().InducesOfValue(searchValue);
    }
}

/// <summary>組み合わせ</summary>
class ModCombination {
    private int mod;
    private int[] fac;
    private int[] finv;
    private int[] inv;

    public ModCombination(int max, int mod = 1000000007) {
        this.mod = mod;
        fac = new int[max]; // modが法のn!の列

        inv = new int[max]; // indexの逆元の列
        finv = new int[max]; // modを法とした、invの累積の累積

        fac[0] = 1; fac[1] = 1;
        finv[0] = 1; finv[1] = 1;
        inv[1] = 1;

        for (int i = 2; i < max; i++) {
            fac[i] = (int)(Math.BigMul(fac[i - 1], i) % mod);  // factorを求める
            var s = Math.BigMul(inv[mod % i], mod / i) % mod;
            inv[i] = (int)(mod - s);　 // iに対する逆元を求める
            finv[i] = (int)(Math.BigMul(finv[i - 1], inv[i]) % mod);
        }
    }
    /// <summary>nCk</summary>
    public int Calculation(int n, int k) {
        if (n < k) return 0;
        if (n < 0 || k < 0) return 0;

        return (int)(Math.BigMul(fac[n], (int)(Math.BigMul(finv[k], finv[n - k]) % mod)) % mod);
    }
}

/// <summary>あまりを計算するためのintのラッパー</summary>
struct ModInt {
    int Value { get; }
    int Mod { get; }

    public override string ToString() {
        return Value.ToString();
    }

    public static explicit operator ModInt(long value) {
        return new ModInt(value);
    }

    public ModInt(long value, int mod = 1000000007) {
        this.Value = (int)(value % mod);
        this.Mod = mod;
    }

    public static explicit operator int(ModInt modInt) {
        return modInt.Value;
    }

    private static void CheckNotEqualMod(ModInt item1, ModInt item2) {
        if (item1.Mod != item2.Mod) {
            throw new Exception("mod not equal");
        }
    }

    public static ModInt operator +(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value + item2.Value;
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = Math.BigMul(item1.Value, item2.Value);
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, ModInt item2) {
        CheckNotEqualMod(item1, item2);
        long value = item1.Value - item2.Value;
        if (value < 0) {
            value += item1.Mod;
        }
        return new ModInt(value, item1.Mod);
    }

    public static ModInt operator +(ModInt item1, int item2) {
        return item1 + new ModInt(item2, item1.Mod);
    }

    public static ModInt operator *(ModInt item1, int item2) {
        return item1 * new ModInt(item2, item1.Mod);
    }

    public static ModInt operator -(ModInt item1, int item2) {
        return item1 - new ModInt(item2, item1.Mod);
    }


    public override int GetHashCode() {
        return this.Value;
    }

    public override bool Equals(object obj) {
        if (obj == null || this.GetType() != obj.GetType()) {
            return false;
        }

        var modIntObj = (ModInt)obj;
        return modIntObj.Value == this.Value;
    }

    public static bool operator ==(ModInt item1, ModInt item2) {
        return item1.Value == item2.Value;
    }

    public static bool operator !=(ModInt item1, ModInt item2) {
        return !(item1 == item2);
    }

    public static bool operator ==(ModInt item1, int item2) {
        return item1.Value == item2;
    }

    public static bool operator !=(ModInt item1, int item2) {
        return !(item1.Value == item2);
    }

    public static bool operator >=(ModInt item1, ModInt item2) {
        return item1.Value >= item2.Value;
    }

    public static bool operator <=(ModInt item1, ModInt item2) {
        return item1.Value <= item2.Value;
    }

    public static bool operator >=(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <=(ModInt item1, int item2) {
        return item1.Value <= item2;
    }

    public static bool operator >(ModInt item1, ModInt item2) {
        return item1.Value > item2.Value;
    }

    public static bool operator <(ModInt item1, ModInt item2) {
        return item1.Value < item2.Value;
    }

    public static bool operator >(ModInt item1, int item2) {
        return item1.Value >= item2;
    }

    public static bool operator <(ModInt item1, int item2) {
        return item1.Value <= item2;
    }
}

// optional

namespace Graph {
    /// <summary>無向グラフ</summary>
    class UndirectedGraph {
        public Dictionary<int, List<int>> Graph { get; }

        /// <param name="graphArr">point1 to point2</param>
        /// <param name="n">グラフの全要素数</param>
        public UndirectedGraph(int[][] graphArr, int n) {
            Graph = new Dictionary<int, List<int>>();

            for (int i = 1; i <= n; i++) {
                Graph.Add(i, new List<int>());
            }

            foreach (var arr in graphArr) {
                Graph[arr[0]].Add(arr[1]);
                Graph[arr[1]].Add(arr[0]);
            }
        }
    }

    /// <summary>有向グラフの隣接行列表現.0は全ノードへつながることができる根とする</summary>
    class SimpleAdjacencyMatrix {
        public int[][] Matrix { get; }

        public SimpleAdjacencyMatrix(int[][] matrix) {
            this.Matrix = matrix;
        }
        /// <summary>ノード数nの隣接行列表現を生成</summary>
        public SimpleAdjacencyMatrix(int n, int defaultVal = 0) {
            Matrix = new int[n + 1][];
            for (int i = 0; i <= n; i++) {
                Matrix[i] = new int[n + 1];
                for (int j = 0; j <= n; j++) {
                    Matrix[i][j] = defaultVal;
                }
            }
        }

        /// <summary>
        /// 辺の追加 (行列に1を足す)
        /// </summary>
        /// <param name="nodeNo">元ノードのNo</param>
        /// <param name="outDegree">出次数</param>
        /// <param name="outNodeNos">向かう先のノードNo</param>
        public void AddEdgeVal(int nodeNo, int outDegree, int[] outNodeNos) {
            foreach (var outNodeNo in outNodeNos) {
                Matrix[nodeNo][outNodeNo] = 1;
            }
        }

        /// <summary>
        /// 辺の削除 (行列を0にする)
        /// </summary>
        /// <param name="nodeNo">元ノードのNo</param>
        /// <param name="outDegree">出次数</param>
        /// <param name="outNodeNos">向かう先のノードNo</param>
        public void DelEdgeVal(int nodeNo, int outDegree, int[] outNodeNos) {
            foreach (var outNodeNo in outNodeNos) {
                Matrix[nodeNo][outNodeNo] = 0;
            }
        }
    }

    enum Color {
        White, Gray, Black
    }
}



Measured memory_bytes: 18544