import java.io.*;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

import static java.lang.Math.*;

public class Main {

    void solve() throws Throwable {
        int n = readInt();
        Set<Point> points = new HashSet<>();
        for (int i = 0; i < n; i++) {
            points.add(new Point(i + 1, readInt(), readInt(), readInt()));
        }
        List<Pair> answer = new ArrayList<>();

        work(points, answer, (Function<Point, ?>) p -> new Point2D(p.x, p.y));
        work(points, answer, (Function<Point, ?>) p -> new Point2D(p.x, p.z));
        work(points, answer, (Function<Point, ?>) p -> new Point2D(p.y, p.z));

        work(points, answer, (Function<Point, ?>) p -> p.x);
        work(points, answer, (Function<Point, ?>) p -> p.y);
        work(points, answer, (Function<Point, ?>) p -> p.z);

        work(points, answer, p -> 0);

        for (Pair pair : answer) {
            System.out.println(pair.a.index + " " + pair.b.index);
        }
    }

    private void work(Set<Point> points, List<Pair> answer, Function<Point, ?> classifier) {
        Map<?, List<Point>> subsets = points.stream().collect(Collectors.groupingBy(classifier));
        for (List<Point> subset : subsets.values()) {
            List<Pair> pairs = solveSubset(subset);
            answer.addAll(pairs);
            for (Pair pair : pairs) {
                points.remove(pair.a);
                points.remove(pair.b);
            }
        }
    }

    static List<Pair> solveSubset(List<Point> a) {
        Comparator<Point> comparator = Comparator.comparing((Point p) -> p.x).thenComparing(p -> p.y).thenComparing(p -> p.z);
        a.sort(comparator);
        List<Pair> ans = new ArrayList<>();
        for (int i = 0; i < a.size() / 2; i++) {
            ans.add(new Pair(a.get(i * 2), a.get(i * 2 + 1)));
        }
        return ans;
    }

    static class Pair {

        final Point a, b;

        Pair(Point a, Point b) {
            this.a = a;
            this.b = b;
        }

    }

    static class Point2D {
        final int x, y;

        Point2D(int x, int y) {
            this.x = x;
            this.y = y;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Point2D point2D = (Point2D) o;
            return x == point2D.x &&
                    y == point2D.y;
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y);
        }
    }

    static class Point {

        final int index, x, y, z;

        Point(int index, int x, int y, int z) {
            this.index = index;
            this.x = x;
            this.y = y;
            this.z = z;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            Point point = (Point) o;
            return index == point.index &&
                    x == point.x &&
                    y == point.y &&
                    z == point.z;
        }

        @Override
        public int hashCode() {
            return Objects.hash(index, x, y, z);
        }
    }

    //-------------------------------------------------

    final boolean ONLINE_JUDGE = !new File("input.txt").exists();

    BufferedReader in;
    PrintWriter out;
    StringTokenizer tok;

    public void run() {
        Runnable run = () -> {
            try {
                long startTime = System.currentTimeMillis();
                Locale.setDefault(Locale.US);
                if (ONLINE_JUDGE) {
                    in = new BufferedReader(new InputStreamReader(System.in));
                    out = new PrintWriter(System.out);
                } else {
                    in = new BufferedReader(new FileReader("input.txt"));
                    out = new PrintWriter("output.txt");
                }
                tok = new StringTokenizer("");
                solve();
                in.close();
                out.close();
                long endTime = System.currentTimeMillis();
                long totalMemory = Runtime.getRuntime().totalMemory();
                long freeMemory = Runtime.getRuntime().freeMemory();
                System.err.println();
                System.err.println("Time = " + (endTime - startTime) + " ms");
                //System.err.println("Memory = " + ((totalMemory - freeMemory) / 1024) + " KB");
            } catch (Throwable e) {
                e.printStackTrace(System.err);
                System.exit(-1);
            }
        };
        new Thread(null, run, "run", 256 * 1024 * 1024).start();
        min(0, 0);
    }

    String readString() {
        while (!tok.hasMoreTokens()) {
            String line;
            try {
                line = in.readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (line == null) return null;
            tok = new StringTokenizer(line);
        }
        return tok.nextToken();
    }

    int readInt() {
        return Integer.parseInt(readString());
    }

    long readLong() throws IOException {
        return Long.parseLong(readString());
    }

    double readDouble() throws IOException {
        return Double.parseDouble(readString());
    }

    void debug(Object... o) {
        if (!ONLINE_JUDGE) {
            System.err.println(Arrays.deepToString(o));
        }
    }

    /*long binpow(long a, long n) {
        long r = 1;
        while (n > 0) {
            if ((n & 1) > 0) {
                r *= a;
            }
            a *= a;
            n /= 2;
        }
        return r;
    }/**/

    long binpow(long a, long n, long mod) {
        long r = 1;
        while (n > 0) {
            if ((n & 1) > 0) {
                r = (r * a) % mod;
            }
            a = (a * a) % mod;
            n /= 2;
        }
        return r;
    }/**/

    static long gcd(long x, long y) {
        return y == 0 ? x : gcd(y, x % y);
    }

    private long[] readLongArray(int n) throws IOException {
        long[] a = new long[n];
        for (int i = 0; i < n; i++) {
            a[i] = readLong();
        }
        return a;
    }

    private int[] readIntArray(int n) throws IOException {
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = readInt();
        }
        return a;
    }

    public static void main(String[] args) {
        new Main().run();
    }
}


import java.io.*;
import java.util.*;

import static java.lang.Math.*;

public class Main {

    void solve() throws Throwable {
        int n = readInt();
        Point[] a = new Point[n];
        for (int i = 0; i < n; i++) {
            a[i] = new Point(i + 1, readInt(), readInt(), readInt());
        }
        Arrays.sort(a, Comparator.comparing((Point p) -> p.x).thenComparing(p -> p.y).thenComparing(p -> p.z));
        for (int i = 0; i < n / 2; i++) {
            System.out.println(a[i * 2].index + " " + a[i * 2 + 1].index);
        }
    }

    static class Point {
        final int index, x, y, z;

        Point(int index, int x, int y, int z) {
            this.index = index;
            this.x = x;
            this.y = y;
            this.z = z;
        }
    }

    //-------------------------------------------------

    final boolean ONLINE_JUDGE = !new File("input.txt").exists();

    BufferedReader in;
    PrintWriter out;
    StringTokenizer tok;

    public void run() {
        Runnable run = () -> {
            try {
                long startTime = System.currentTimeMillis();
                Locale.setDefault(Locale.US);
                if (ONLINE_JUDGE) {
                    in = new BufferedReader(new InputStreamReader(System.in));
                    out = new PrintWriter(System.out);
                } else {
                    in = new BufferedReader(new FileReader("input.txt"));
                    out = new PrintWriter("output.txt");
                }
                tok = new StringTokenizer("");
                solve();
                in.close();
                out.close();
                long endTime = System.currentTimeMillis();
                long totalMemory = Runtime.getRuntime().totalMemory();
                long freeMemory = Runtime.getRuntime().freeMemory();
                System.err.println();
                System.err.println("Time = " + (endTime - startTime) + " ms");
                //System.err.println("Memory = " + ((totalMemory - freeMemory) / 1024) + " KB");
            } catch (Throwable e) {
                e.printStackTrace(System.err);
                System.exit(-1);
            }
        };
        new Thread(null, run, "run", 256 * 1024 * 1024).start();
        min(0, 0);
    }

    String readString() {
        while (!tok.hasMoreTokens()) {
            String line;
            try {
                line = in.readLine();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            if (line == null) return null;
            tok = new StringTokenizer(line);
        }
        return tok.nextToken();
    }

    int readInt() {
        return Integer.parseInt(readString());
    }

    long readLong() throws IOException {
        return Long.parseLong(readString());
    }

    double readDouble() throws IOException {
        return Double.parseDouble(readString());
    }

    void debug(Object... o) {
        if (!ONLINE_JUDGE) {
            System.err.println(Arrays.deepToString(o));
        }
    }

    /*long binpow(long a, long n) {
        long r = 1;
        while (n > 0) {
            if ((n & 1) > 0) {
                r *= a;
            }
            a *= a;
            n /= 2;
        }
        return r;
    }/**/

    long binpow(long a, long n, long mod) {
        long r = 1;
        while (n > 0) {
            if ((n & 1) > 0) {
                r = (r * a) % mod;
            }
            a = (a * a) % mod;
            n /= 2;
        }
        return r;
    }/**/

    static long gcd(long x, long y) {
        return y == 0 ? x : gcd(y, x % y);
    }

    private long[] readLongArray(int n) throws IOException {
        long[] a = new long[n];
        for (int i = 0; i < n; i++) {
            a[i] = readLong();
        }
        return a;
    }

    private int[] readIntArray(int n) throws IOException {
        int[] a = new int[n];
        for (int i = 0; i < n; i++) {
            a[i] = readInt();
        }
        return a;
    }

    public static void main(String[] args) {
        new Main().run();
    }
}


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Scanner;

public class gr5c {
	public static void main(String[] args) {
		Scanner scan=new Scanner(System.in);
		int n=scan.nextInt();
		pt[] a=new pt[n];
		for(int i=0;i<n;i++) a[i]=new pt(scan.nextInt(),scan.nextInt(),scan.nextInt(),i+1);
		Arrays.sort(a);
		boolean[] taken=new boolean[n+1];

		//same line
		for(int i=0;i+1<n;i++) {
			if(taken[i]) continue;
			if(a[i].x==a[i+1].x&&a[i].y==a[i+1].y) {
				System.out.println(a[i].id+" "+a[i+1].id);
				taken[i]=taken[i+1]=true;
			}
		}
		ArrayList<Integer> rem=new ArrayList<>();
		for(int i=0;i<n;i++) {
			if(!taken[i]) rem.add(i);
		}
		//same plane
		for(int i=0;i+1<rem.size();i++) {
			if(taken[rem.get(i)]) continue;
			if(a[rem.get(i)].x==a[rem.get(i+1)].x) {
				System.out.println(a[rem.get(i)].id+" "+a[rem.get(i+1)].id);
				taken[rem.get(i)]=taken[rem.get(i+1)]=true;
			}
		}
		rem=new ArrayList<>();
		for(int i=0;i<n;i++) {
			if(!taken[i]) rem.add(i);
		}
		//everything else
		for(int i=0;i+1<rem.size();i+=2) {
			System.out.println(a[rem.get(i)].id+" "+a[rem.get(i+1)].id);
		}
	}
	static class pt implements Comparable<pt> {
		int x,y,z,id;
		pt(int x, int y, int z, int id) {
			this.x=x;
			this.y=y;
			this.z=z;
			this.id=id;
		}
		@Override
		public int compareTo(pt o) {
			if(x==o.x) {
				if(y==o.y) {
					return z-o.z;
				}
				return y-o.y;
			}
			return x-o.x;
		}
	}
}


import java.util.*;
import java.io.*;
public class Main
{
    public static void main(String []args) throws IOException {
    	FastScanner in  = new FastScanner(System.in);
    	PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(System.out)), false);
    	solve(in, out);
    	in.close();
    	out.close();
    }
    static long gcd(long a, long b){ return (b==0) ? a : gcd(b, a%b); }
    static int gcd(int a, int b){ return (b==0) ? a : gcd(b, a%b); }

    public static int[] generatePrimes(int n) {
		boolean[] prime = new boolean[n + 1];
		Arrays.fill(prime, 2, n + 1, true);
		for (int i = 2; i * i <= n; i++)
			if (prime[i])
				for (int j = i * i; j <= n; j += i)
					prime[j] = false;
		int[] primes = new int[n + 1];
		int cnt = 0;
		for (int i = 0; i < prime.length; i++)
			if (prime[i])
				primes[cnt++] = i;
		return Arrays.copyOf(primes, cnt);
	}
    static class FastScanner{
	BufferedReader reader;
	StringTokenizer st;
	FastScanner(InputStream stream){reader=new BufferedReader(new InputStreamReader(stream));st=null;}
	String next(){while(st == null || !st.hasMoreTokens()){try{String line = reader.readLine();if(line == null){return null;}
	     st = new StringTokenizer(line);}catch (Exception e){throw new RuntimeException();}}return st.nextToken();}
	String nextLine()  {String s=null;try{s=reader.readLine();}catch(IOException e){e.printStackTrace();}return s;}
	int    nextInt()   {return Integer.parseInt(next());}
	long   nextLong()  {return Long.parseLong(next());}
	double nextDouble(){return Double.parseDouble(next());}
	char   nextChar()  {return next().charAt(0);}
	int[]  nextIntArray(int n)         {int[] arr= new int[n];   int i=0;while(i<n){arr[i++]=nextInt();}  return arr;}
	long[] nextLongArray(int n)        {long[]arr= new long[n];  int i=0;while(i<n){arr[i++]=nextLong();} return arr;}
	int[]  nextIntArrayOneBased(int n) {int[] arr= new int[n+1]; int i=1;while(i<=n){arr[i++]=nextInt();} return arr;}
	long[] nextLongArrayOneBased(int n){long[]arr= new long[n+1];int i=1;while(i<=n){arr[i++]=nextLong();}return arr;}
	void   close(){try{reader.close();}catch(IOException e){e.printStackTrace();}}
    }

    /********* SOLUTION STARTS HERE ************/

    private static void solve(FastScanner in, PrintWriter out){

        int n = in.nextInt();
        point pts[] = new point[n];
        for(int i=0;i<n;i++) pts[i] = new point( in.nextInt(), in.nextInt(), in.nextInt(), i);
        Arrays.sort(pts,new point());
        boolean vis [] = new boolean [n];
        Arrays.fill(vis,false);
        ArrayList<Integer> ans = new ArrayList<Integer>();
        int pt1 = 0, pt2 = 0;
        while(pt1<n && pt2<n)
        {
            while(pt1<n && vis[pt1]) pt1+=1;
            pt2 = pt1+1;
            while(pt2<n && vis[pt2]) pt2+=1;
            if(pt1<n && pt2<n)
            {
                if(pts[pt1].x == pts[pt2].x)
                {
                    if(pts[pt1].y == pts[pt2].y)
                    {
                        ans.add(pts[pt1].ind);
                        ans.add(pts[pt2].ind);
                        vis[pt1] = true;
                        vis[pt2] = true;
                    }
                }
            }
            else break;
            pt1++;
            // pt2++;
        }
        pt1 = 0; pt2 = 0;
        while(pt1<n && pt2<n)
        {
            while(pt1<n && vis[pt1]) pt1++;
            pt2 = pt1+1;
            while(pt2<n && vis[pt2]) pt2++;
            if(pt1<n && pt2<n)
            {
                if(pts[pt1].x == pts[pt2].x)
                {
                    ans.add(pts[pt1].ind);
                    ans.add(pts[pt2].ind);
                    vis[pt1] = true;
                    vis[pt2] = true;
                    // vis[pts[pt1].ind] = true;
                    // vis[pts[pt2].ind] = true;
                }
            }
            else break;
            pt1++;
            // pt2++;
        }
        pt1 = 0; pt2 = 0;
        while(pt1<n && pt2<n)
        {
            while(pt1<n && vis[pt1]) pt1++;
            pt2 = pt1+1;
            while(pt2<n && vis[pt2]) pt2++;
            if(pt1<n && pt2<n)
            {
                ans.add(pts[pt1].ind);
                ans.add(pts[pt2].ind);
                vis[pt1] = true;
                vis[pt2] = true;
                // vis[pts[pt1].ind] = true;
                // vis[pts[pt2].ind] = true;
            }
            else break;
            pt1++;
            // pt2++;
        }
        for(int i = 0;i<ans.size()-1;i+=2) out.write((ans.get(i)+1)+" "+(ans.get(i+1)+1) +"\n");
    }

    /************* SOLUTION ENDS HERE **********/
}
class point implements Comparator<point>
{
    int x,y,z,ind;
    point(int x,int y,int z,int ind)
    {
        this.x = x;
        this.y = y;
        this.z = z;
        this.ind = ind;
    }
    point(){}
    public int compare(point a,point b)
    {
        if(a.x != b.x) return a.x - b.x;
        else if(a.y != b.y) return a.y - b.y;
        else if(a.z != b.z) return a.z - b.z;
        else return a.ind - b.ind;
    }
}


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
		int t = 1;
		for (int i = 0; i < t; i++) {
			int n = in.nextInt();
			int[][] a = getIntArr(in, n, 3);
			solve(n, a);
			System.out.println(sb.toString());
		}

		in.close();
	}

	static StringBuilder sb = new StringBuilder();

	private static void solve(int n, int[][] a) {
		List<Integer> ids = new ArrayList<>();
		for (int i = 0; i < n; i++) {
			ids.add(i);
		}
		dfs(a, ids, 0);
	}

	static int dfs(int[][] a, List<Integer> ids, int k) {
		if (k == 3) {
			return ids.get(0);
		}
		Map<Integer, List<Integer>> m = new HashMap<>();
		for (int id : ids) {
			List<Integer> l = m.getOrDefault(a[id][k], new ArrayList<>());
			l.add(id);
			m.put(a[id][k], l);
		}
		List<Integer> singles = new ArrayList<>();
		for (int key : m.keySet()) {
			int single = dfs(a, m.get(key), k + 1);
			if (single != -1)
				singles.add(single);
		}
		for (int i = 0; i + 1 < singles.size(); i += 2) {
			sb.append((singles.get(i) + 1) + " " + (singles.get(i + 1) + 1) + "\n");
		}
		if (singles.size() % 2 != 0) {
			return singles.get(singles.size() - 1);
		} else {
			return -1;
		}
	}

	static long mod = 1000000007;

	static long add(long a, long b) {
		long r = a + b;
		if (r < 0) {
			r += mod;
		}
		return r % mod;
	}

	static long mul(long a, long b) {
		return (a * b) % mod;
	}

	static int gcd(int a, int b) {
		if (b == 0) {
			return a;
		} else {
			return gcd(b, a % b);
		}
	}

	static String str(Integer[] r) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < r.length; i += 2) {
			sb.append(r[i] + " " + r[i + 1] + "\n");
		}
		return sb.toString();
	}

	static String str(int[][] a) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < a.length; i++) {
			sb.append(a[i][0] + " " + a[i][1] + "\n");
		}
		return sb.toString();
	}

	static int[] getIntArr(Scanner in, int size) {
		int[] arr = new int[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.nextInt();
		}
		return arr;
	}

	static int[][] getIntArr(Scanner in, int row, int col) {
		int[][] arr = new int[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getIntArr(in, col);
		}
		return arr;
	}

	static long[] getLongArr(Scanner in, int size) {
		long[] arr = new long[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.nextLong();
		}
		return arr;
	}

	static long[][] getLongArr(Scanner in, int row, int col) {
		long[][] arr = new long[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getLongArr(in, col);
		}
		return arr;
	}

	static char[] getCharArr(Scanner in, int size) {
		char[] arr = in.next().toCharArray();
		return arr;
	}

	static char[][] getCharArr(Scanner in, int row, int col) {
		char[][] arr = new char[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getCharArr(in, col);
		}
		return arr;
	}

	static String[] getStringArr(Scanner in, int size) {
		String[] arr = new String[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.next();
		}
		return arr;
	}

	static void swap(int[] a, int i, int j) {
		int t = a[i];
		a[i] = a[j];
		a[j] = t;
	}

	static void set(int[][] a, int v) {
		for (int i = 0; i < a.length; i++) {
			for (int j = 0; j < a[i].length; j++) {
				a[i][j] = v;
			}
		}
	}

	static Map<Integer, List<Integer>> getEdges(Scanner in, int size, boolean directed) {
		Map<Integer, List<Integer>> edges = new HashMap<>();
		for (int i = 0; i < size; i++) {
			int from = in.nextInt();
			int to = in.nextInt();
			if (!edges.containsKey(from)) {
				edges.put(from, new ArrayList<Integer>());
			}
			edges.get(from).add(to);
			if (!directed) {
				if (!edges.containsKey(to)) {
					edges.put(to, new ArrayList<Integer>());
				}
				edges.get(to).add(from);
			}

		}
		return edges;
	}

}


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.TreeMap;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(new BufferedReader(new InputStreamReader(System.in)));
		int t = 1;
		for (int i = 0; i < t; i++) {
			int n = in.nextInt();
			int[][] a = getIntArr(in, n, 3);
			solve(n, a);
			System.out.println(sb.toString());
		}

		in.close();
	}

	static StringBuilder sb = new StringBuilder();

	private static void solve(int n, int[][] a) {
		List<Integer> ids = new ArrayList<>();
		for (int i = 0; i < n; i++) {
			ids.add(i);
		}
		dfs(a, ids, 0);
	}

	static int dfs(int[][] a, List<Integer> ids, int k) {
		if (k == 3) {
			return ids.get(0);
		}
		Map<Integer, List<Integer>> m = new TreeMap<>();
		for (int id : ids) {
			List<Integer> l = m.getOrDefault(a[id][k], new ArrayList<>());
			l.add(id);
			m.put(a[id][k], l);
		}
		List<Integer> singles = new ArrayList<>();
		for (int key : m.keySet()) {
			int single = dfs(a, m.get(key), k + 1);
			if (single != -1)
				singles.add(single);
		}
		for (int i = 0; i + 1 < singles.size(); i += 2) {
			sb.append((singles.get(i) + 1) + " " + (singles.get(i + 1) + 1) + "\n");
		}
		if (singles.size() % 2 != 0) {
			return singles.get(singles.size() - 1);
		} else {
			return -1;
		}
	}

	static long mod = 1000000007;

	static long add(long a, long b) {
		long r = a + b;
		if (r < 0) {
			r += mod;
		}
		return r % mod;
	}

	static long mul(long a, long b) {
		return (a * b) % mod;
	}

	static int gcd(int a, int b) {
		if (b == 0) {
			return a;
		} else {
			return gcd(b, a % b);
		}
	}

	static String str(Integer[] r) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < r.length; i += 2) {
			sb.append(r[i] + " " + r[i + 1] + "\n");
		}
		return sb.toString();
	}

	static String str(int[][] a) {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < a.length; i++) {
			sb.append(a[i][0] + " " + a[i][1] + "\n");
		}
		return sb.toString();
	}

	static int[] getIntArr(Scanner in, int size) {
		int[] arr = new int[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.nextInt();
		}
		return arr;
	}

	static int[][] getIntArr(Scanner in, int row, int col) {
		int[][] arr = new int[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getIntArr(in, col);
		}
		return arr;
	}

	static long[] getLongArr(Scanner in, int size) {
		long[] arr = new long[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.nextLong();
		}
		return arr;
	}

	static long[][] getLongArr(Scanner in, int row, int col) {
		long[][] arr = new long[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getLongArr(in, col);
		}
		return arr;
	}

	static char[] getCharArr(Scanner in, int size) {
		char[] arr = in.next().toCharArray();
		return arr;
	}

	static char[][] getCharArr(Scanner in, int row, int col) {
		char[][] arr = new char[row][];
		for (int i = 0; i < row; i++) {
			arr[i] = getCharArr(in, col);
		}
		return arr;
	}

	static String[] getStringArr(Scanner in, int size) {
		String[] arr = new String[size];
		for (int i = 0; i < size; i++) {
			arr[i] = in.next();
		}
		return arr;
	}

	static void swap(int[] a, int i, int j) {
		int t = a[i];
		a[i] = a[j];
		a[j] = t;
	}

	static void set(int[][] a, int v) {
		for (int i = 0; i < a.length; i++) {
			for (int j = 0; j < a[i].length; j++) {
				a[i][j] = v;
			}
		}
	}

	static Map<Integer, List<Integer>> getEdges(Scanner in, int size, boolean directed) {
		Map<Integer, List<Integer>> edges = new HashMap<>();
		for (int i = 0; i < size; i++) {
			int from = in.nextInt();
			int to = in.nextInt();
			if (!edges.containsKey(from)) {
				edges.put(from, new ArrayList<Integer>());
			}
			edges.get(from).add(to);
			if (!directed) {
				if (!edges.containsKey(to)) {
					edges.put(to, new ArrayList<Integer>());
				}
				edges.get(to).add(from);
			}

		}
		return edges;
	}

}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

public class C {

	public static void main(String[] args) {
		FastScanner fs=new FastScanner();
		int n=fs.nextInt();
		Vec[] a=new Vec[n];
		for (int i=0; i<n; i++) a[i]=new Vec(fs.nextInt(), fs.nextInt(), fs.nextInt(), i);
		TreeSet<Vec> exist=new TreeSet<>();
		for (Vec v:a) exist.add(v);
		TreeMap<Integer, TreeMap<Integer, TreeMap<Integer, Vec>>> xyz=new TreeMap<>();
		for (Vec v:a) {
			if (!xyz.containsKey(v.x)) xyz.put(v.x, new TreeMap<>());
			TreeMap<Integer, TreeMap<Integer, Vec>> yz=xyz.get(v.x);
			if (!yz.containsKey(v.y)) yz.put(v.y, new TreeMap<>());
			TreeMap<Integer, Vec> z=yz.get(v.y);
			z.put(v.z, v);
		}
		for (TreeMap<Integer, TreeMap<Integer, Vec>> yz:xyz.values()) {
			for (TreeMap<Integer, Vec> z:yz.values()) {
				while (z.size()>1) {
					Vec first=z.firstEntry().getValue();
					z.remove(z.firstKey());
					Vec second=z.firstEntry().getValue();
					z.remove(z.firstKey());
					first.dead=second.dead=true;
					System.out.println(first.index+" "+second.index);
				}
			}
		}
		TreeMap<Integer, TreeMap<Integer, Vec>> xy=new TreeMap<>();
		for (Vec v:a) {
			if (v.dead)
				continue;
			if (!xy.containsKey(v.x)) xy.put(v.x, new TreeMap<>());
			TreeMap<Integer, Vec> y=xy.get(v.x);
			y.put(v.y, v);
		}
		for (TreeMap<Integer, Vec> y:xy.values()) {
			while (y.size()>1) {
				Vec first=y.firstEntry().getValue();
				y.remove(y.firstKey());
				Vec second=y.firstEntry().getValue();
				y.remove(y.firstKey());
				first.dead=second.dead=true;
				System.out.println(first.index+" "+second.index);
			}
		}
		ArrayList<Vec> points=new ArrayList<>();
		for (Vec v:a) if (!v.dead) points.add(v);
		Collections.sort(points);
		for (int i=0; i<points.size(); i+=2) {
			System.out.println(points.get(i).index+" "+points.get(i+1).index);
		}
	}

	static class Vec implements Comparable<Vec> {
		int index;
		boolean dead=false;
		int x, y, z;
		public Vec(int x, int y, int z, int index) {
			this.x=x;
			this.y=y;
			this.z=z;
			this.index=index+1;
		}

		public int compareTo(Vec o) {
			if (x!=o.x) return Integer.compare(x, o.x);
			if (y!=o.y) return Integer.compare(y, o.y);
			return Integer.compare(z, o.z);
		}
	}

	static class FastScanner {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st=new StringTokenizer("");
		public String next() {
			while (!st.hasMoreElements())
				try {
					st=new StringTokenizer(br.readLine());
				} catch (IOException e) {
					e.printStackTrace();
				}
			return st.nextToken();
		}

		public int nextInt() {
			return Integer.parseInt(next());
		}
	}

}


/**
 * Date: 17 Oct, 2019
 * Link:
 *
 * @author Prasad Chaudhari
 * @linkedIn: https://www.linkedin.com/in/prasad-chaudhari-841655a6/
 * @git: https://github.com/Prasad-Chaudhari
 */
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.Map;
import java.util.TreeMap;

public class newProgram0 {

    public static void main(String[] args) throws IOException {
        // TODO code application logic here
        FastIO in = new FastIO();
        TreeMap<Integer, TreeMap<Integer, LinkedList<int[]>>> map = new TreeMap<>();
        int n = in.ni();
        int a[][] = new int[n][];
        for (int i = 0; i < n; i++) {
            a[i] = new int[] { in.ni(), in.ni(), in.ni(), i + 1 };
        }
        Comparator<int[]> c = new Comparator<int[]>() {
            @Override
            public int compare(int[] a, int[] b) {
                if (a[0] == b[0]) {
                    if (a[1] == b[1]) {
                        return Integer.compare(a[2], b[2]);
                    }
                    return Integer.compare(a[1], b[1]);
                }
                return Integer.compare(a[0], b[0]);
            }
        };
        Arrays.sort(a, c);
        LinkedList<int[]> ans = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            if (!map.containsKey(a[i][0])) {
                map.put(a[i][0], new TreeMap<>());
            }
            if (!map.get(a[i][0]).containsKey(a[i][1])) {
                map.get(a[i][0]).put(a[i][1], new LinkedList<>());
            }
            map.get(a[i][0]).get(a[i][1]).add(a[i]);
        }
        LinkedList<int[]> r_x = new LinkedList<>();
        for (TreeMap<Integer, LinkedList<int[]>> m : map.values()) {
            LinkedList<int[]> r_y = new LinkedList<>();
            int i = Integer.MIN_VALUE;
            while (m.higherKey(i) != null) {
                i = m.higherKey(i);
                while (m.get(i).size() > 1) {
                    int a1[] = m.get(i).remove();
                    int a2[] = m.get(i).remove();
                    ans.add(new int[] { a2[3], a1[3] });
                }
                while (!m.get(i).isEmpty()) {
                    r_y.add(m.get(i).remove());
                }
            }
            while (r_y.size() > 1) {
                int a1[] = r_y.remove();
                int a2[] = r_y.remove();
                ans.add(new int[] { a2[3], a1[3] });
            }
            while (!r_y.isEmpty()) {
                r_x.add(r_y.remove());
            }
        }
        while (r_x.size() > 1) {
            int a1[] = r_x.remove();
            int a2[] = r_x.remove();
            ans.add(new int[] { a2[3], a1[3] });
        }
        while (!ans.isEmpty()) {
            int a1[] = ans.remove();
            in.println(a1[0] + " " + a1[1]);
        }

        in.bw.flush();
    }

    static class Data implements Comparable<Data> {

        int a, b;

        public Data(int a, int b) {
            this.a = a;
            this.b = b;
        }

        @Override
        public int compareTo(Data o) {
            if (a == o.a) {
                return Integer.compare(b, o.b);
            }
            return Integer.compare(a, o.a);
        }

        public static void sort(int a[]) {
            Data d[] = new Data[a.length];
            for (int i = 0; i < a.length; i++) {
                d[i] = new Data(a[i], 0);
            }
            Arrays.sort(d);
            for (int i = 0; i < a.length; i++) {
                a[i] = d[i].a;
            }
        }
    }

    static class FastIO {

        private final BufferedReader br;
        private final BufferedWriter bw;
        private String s[];
        private int index;

        public FastIO() throws IOException {
            br = new BufferedReader(new InputStreamReader(System.in));
            bw = new BufferedWriter(new OutputStreamWriter(System.out, "UTF-8"));
            s = br.readLine().split(" ");
            index = 0;
        }

        public int ni() throws IOException {
            return Integer.parseInt(nextToken());
        }

        public double nd() throws IOException {
            return Double.parseDouble(nextToken());
        }

        public long nl() throws IOException {
            return Long.parseLong(nextToken());
        }

        public String next() throws IOException {
            return nextToken();
        }

        public String nli() throws IOException {
            try {
                return br.readLine();
            } catch (IOException ex) {

            }
            return null;
        }

        public int[] gia(int n) throws IOException {
            int a[] = new int[n];
            for (int i = 0; i < n; i++) {
                a[i] = ni();
            }
            return a;
        }

        public int[] gia(int n, int start, int end) throws IOException {
            validate(n, start, end);
            int a[] = new int[n];
            for (int i = start; i < end; i++) {
                a[i] = ni();
            }
            return a;
        }

        public double[] gda(int n) throws IOException {
            double a[] = new double[n];
            for (int i = 0; i < n; i++) {
                a[i] = nd();
            }
            return a;
        }

        public double[] gda(int n, int start, int end) throws IOException {
            validate(n, start, end);
            double a[] = new double[n];
            for (int i = start; i < end; i++) {
                a[i] = nd();
            }
            return a;
        }

        public long[] gla(int n) throws IOException {
            long a[] = new long[n];
            for (int i = 0; i < n; i++) {
                a[i] = nl();
            }
            return a;
        }

        public long[] gla(int n, int start, int end) throws IOException {
            validate(n, start, end);
            long a[] = new long[n];
            for (int i = start; i < end; i++) {
                a[i] = nl();
            }
            return a;
        }

        public int[][][] gwtree(int n) throws IOException {
            int m = n - 1;
            int adja[][] = new int[n + 1][];
            int weight[][] = new int[n + 1][];
            int from[] = new int[m];
            int to[] = new int[m];
            int count[] = new int[n + 1];
            int cost[] = new int[n + 1];
            for (int i = 0; i < m; i++) {
                from[i] = i + 1;
                to[i] = ni();
                cost[i] = ni();
                count[from[i]]++;
                count[to[i]]++;
            }
            for (int i = 0; i <= n; i++) {
                adja[i] = new int[count[i]];
                weight[i] = new int[count[i]];
            }
            for (int i = 0; i < m; i++) {
                adja[from[i]][count[from[i]] - 1] = to[i];
                adja[to[i]][count[to[i]] - 1] = from[i];
                weight[from[i]][count[from[i]] - 1] = cost[i];
                weight[to[i]][count[to[i]] - 1] = cost[i];
                count[from[i]]--;
                count[to[i]]--;
            }
            return new int[][][] { adja, weight };
        }

        public int[][][] gwg(int n, int m) throws IOException {
            int adja[][] = new int[n + 1][];
            int weight[][] = new int[n + 1][];
            int from[] = new int[m];
            int to[] = new int[m];
            int count[] = new int[n + 1];
            int cost[] = new int[n + 1];
            for (int i = 0; i < m; i++) {
                from[i] = ni();
                to[i] = ni();
                cost[i] = ni();
                count[from[i]]++;
                count[to[i]]++;
            }
            for (int i = 0; i <= n; i++) {
                adja[i] = new int[count[i]];
                weight[i] = new int[count[i]];
            }
            for (int i = 0; i < m; i++) {
                adja[from[i]][count[from[i]] - 1] = to[i];
                adja[to[i]][count[to[i]] - 1] = from[i];
                weight[from[i]][count[from[i]] - 1] = cost[i];
                weight[to[i]][count[to[i]] - 1] = cost[i];
                count[from[i]]--;
                count[to[i]]--;
            }
            return new int[][][] { adja, weight };
        }

        public int[][] gtree(int n) throws IOException {
            int adja[][] = new int[n + 1][];
            int from[] = new int[n - 1];
            int to[] = new int[n - 1];
            int count[] = new int[n + 1];
            for (int i = 0; i < n - 1; i++) {
                from[i] = i + 1;
                to[i] = ni();
                count[from[i]]++;
                count[to[i]]++;
            }
            for (int i = 0; i <= n; i++) {
                adja[i] = new int[count[i]];
            }
            for (int i = 0; i < n - 1; i++) {
                adja[from[i]][--count[from[i]]] = to[i];
                adja[to[i]][--count[to[i]]] = from[i];
            }
            return adja;
        }

        public int[][] gg(int n, int m) throws IOException {
            int adja[][] = new int[n + 1][];
            int from[] = new int[m];
            int to[] = new int[m];
            int count[] = new int[n + 1];
            for (int i = 0; i < m; i++) {
                from[i] = ni();
                to[i] = ni();
                count[from[i]]++;
                count[to[i]]++;
            }
            for (int i = 0; i <= n; i++) {
                adja[i] = new int[count[i]];
            }
            for (int i = 0; i < m; i++) {
                adja[from[i]][--count[from[i]]] = to[i];
                adja[to[i]][--count[to[i]]] = from[i];
            }
            return adja;
        }

        public void print(String s) throws IOException {
            bw.write(s);
        }

        public void println(String s) throws IOException {
            bw.write(s);
            bw.newLine();
        }

        public void print(int s) throws IOException {
            bw.write(s + "");
        }

        public void println(int s) throws IOException {
            bw.write(s + "");
            bw.newLine();
        }

        public void print(long s) throws IOException {
            bw.write(s + "");
        }

        public void println(long s) throws IOException {
            bw.write(s + "");
            bw.newLine();
        }

        public void print(double s) throws IOException {
            bw.write(s + "");
        }

        public void println(double s) throws IOException {
            bw.write(s + "");
            bw.newLine();
        }

        private String nextToken() throws IndexOutOfBoundsException, IOException {
            if (index == s.length) {
                s = br.readLine().split(" ");
                index = 0;
            }
            return s[index++];
        }

        private void validate(int n, int start, int end) {
            if (start < 0 || end >= n) {
                throw new IllegalArgumentException();
            }
        }
    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.BufferedWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.ArrayList;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Say My Name
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        C1BalancedRemovalsEasier solver = new C1BalancedRemovalsEasier();
        solver.solve(1, in, out);
        out.close();
    }

    static class C1BalancedRemovalsEasier {
        public void solve(int testNumber, InputReader c, OutputWriter w) {
            int n = c.readInt();
            Point[] p = new Point[n];
            for (int i = 0; i < n; ++i) {
                p[i] = new Point(c.readInt(), c.readInt(), c.readInt(), i + 1);
            }
            Arrays.sort(p);
            ArrayList<Point> arr = new ArrayList<>();
            for (int i = 0; i < n - 1; ++i) {
                if (comp1(p[i], p[i + 1])) {
                    w.printLine(p[i].ind + " " + p[i + 1].ind);
                    i++;
                } else {
                    arr.add(p[i]);
                    if (i == n - 2) {
                        arr.add(p[i + 1]);
                    }
                }
            }
            ArrayList<Point> arr2 = new ArrayList<>();
            for (int i = 0; i < arr.size() - 1; ++i) {
                if (comp2(arr.get(i), arr.get(i + 1))) {
                    w.printLine(arr.get(i).ind + " " + arr.get(i + 1).ind);
                    ++i;
                } else {
                    arr2.add(arr.get(i));
                    if (i == arr.size() - 2) {
                        arr2.add(arr.get(arr.size() - 1));
                    }
                }
            }

            for (int i = 0; i < arr2.size(); i += 2) {
                w.printLine(arr2.get(i).ind + " " + arr2.get(i + 1).ind);
            }

        }

        private boolean comp2(Point p1, Point p2) {
            return p1.x == p2.x;
        }

        private boolean comp1(Point p1, Point p2) {
            return p1.x == p2.x && p1.y == p2.y;
        }

    }

    static class Point implements Comparable<Point> {
        int x;
        int y;
        int z;
        int ind;

        public Point(int x, int y, int z, int ind) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.ind = ind;
        }

        public Point(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public int compareTo(Point o) {
            if (this.x == o.x) {
                if (this.y == o.y) {
                    return this.z - o.z;
                }
                return this.y - o.y;
            }
            return this.x - o.x;
        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }

        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void printLine(Object... objects) {
            print(objects);
            writer.println();
        }

        public void close() {
            writer.close();
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {
            if (numChars == -1) {
                throw new InputMismatchException();
            }
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0) {
                    return -1;
                }
            }
            return buf[curChar++];
        }

        public int readInt() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9') {
                    throw new InputMismatchException();
                }
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public boolean isSpaceChar(int c) {
            if (filter != null) {
                return filter.isSpaceChar(c);
            }
            return isWhitespace(c);
        }

        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);

        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.PriorityQueue;
import java.io.BufferedWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.AbstractCollection;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Say My Name
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        C1BalancedRemovalsEasier solver = new C1BalancedRemovalsEasier();
        solver.solve(1, in, out);
        out.close();
    }

    static class C1BalancedRemovalsEasier {
        public void solve(int testNumber, InputReader c, OutputWriter w) {
            int n = c.readInt();
            PriorityQueue<Point> pq = new PriorityQueue<>();
            for (int i = 0; i < n; ++i) {
                pq.add(new Point(c.readInt(), c.readInt(), c.readInt(), i + 1));
            }
            PriorityQueue<Point> pq1 = new PriorityQueue<>();
            while (!pq.isEmpty()) {
                Point p1 = pq.poll();
                if (!pq.isEmpty()) {
                    Point p2 = pq.poll();
                    if (comp1(p1, p2)) {
                        w.printLine(p1.ind + " " + p2.ind);
                    } else {
                        pq1.add(p1);
                        pq1.add(p2);
                    }
                } else {
                    pq1.add(p1);
                }
            }
            PriorityQueue<Point> pq2 = new PriorityQueue<>();
            while (!pq1.isEmpty()) {
                Point p1 = pq1.poll();
                if (!pq1.isEmpty()) {
                    Point p2 = pq1.poll();
                    if (comp2(p1, p2)) {
                        w.printLine(p1.ind + " " + p2.ind);
                    } else {
                        pq2.add(p1);
                        pq2.add(p2);
                    }
                } else {
                    pq2.add(p1);
                }
            }

            while (!pq2.isEmpty()) {
                w.printLine(pq2.poll().ind + " " + pq2.poll().ind);
            }

        }

        private boolean comp2(Point p1, Point p2) {
            return p1.x == p2.x;
        }

        private boolean comp1(Point p1, Point p2) {
            return p1.x == p2.x && p1.y == p2.y;
        }

    }

    static class Point implements Comparable<Point> {
        int x;
        int y;
        int z;
        int ind;

        public Point(int x, int y, int z, int ind) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.ind = ind;
        }

        public Point(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public int compareTo(Point o) {
            if (this.x == o.x) {
                if (this.y == o.y) {
                    return this.z - o.z;
                }
                return this.y - o.y;
            }
            return this.x - o.x;
        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }

        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void printLine(Object... objects) {
            print(objects);
            writer.println();
        }

        public void close() {
            writer.close();
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {
            if (numChars == -1) {
                throw new InputMismatchException();
            }
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0) {
                    return -1;
                }
            }
            return buf[curChar++];
        }

        public int readInt() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9') {
                    throw new InputMismatchException();
                }
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public boolean isSpaceChar(int c) {
            if (filter != null) {
                return filter.isSpaceChar(c);
            }
            return isWhitespace(c);
        }

        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);

        }

    }
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.PriorityQueue;
import java.io.BufferedWriter;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.AbstractCollection;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Say My Name
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        InputReader in = new InputReader(inputStream);
        OutputWriter out = new OutputWriter(outputStream);
        C1BalancedRemovalsEasier solver = new C1BalancedRemovalsEasier();
        solver.solve(1, in, out);
        out.close();
    }

    static class C1BalancedRemovalsEasier {
        public void solve(int testNumber, InputReader c, OutputWriter w) {
            int n = c.readInt();
            PriorityQueue<Point> pq = new PriorityQueue<>();
            for (int i = 0; i < n; ++i) {
                pq.add(new Point(c.readInt(), c.readInt(), c.readInt(), i + 1));
            }
            PriorityQueue<Point> pq1 = new PriorityQueue<>();
            while (!pq.isEmpty()) {
                Point p1 = pq.poll();
                if (!pq.isEmpty()) {
                    Point p2 = pq.poll();
                    if (comp1(p1, p2)) {
                        w.printLine(p1.ind + " " + p2.ind);
                    } else {
                        pq1.add(p1);
                        pq.add(p2);
                    }
                } else {
                    pq1.add(p1);
                }
            }
            PriorityQueue<Point> pq2 = new PriorityQueue<>();
            while (!pq1.isEmpty()) {
                Point p1 = pq1.poll();
                if (!pq1.isEmpty()) {
                    Point p2 = pq1.poll();
                    if (comp2(p1, p2)) {
                        w.printLine(p1.ind + " " + p2.ind);
                    } else {
                        pq2.add(p1);
                        pq1.add(p2);
                    }
                } else {
                    pq2.add(p1);
                }
            }

            while (!pq2.isEmpty()) {
                w.printLine(pq2.poll().ind + " " + pq2.poll().ind);
            }

        }

        private boolean comp2(Point p1, Point p2) {
            return p1.x == p2.x;
        }

        private boolean comp1(Point p1, Point p2) {
            return p1.x == p2.x && p1.y == p2.y;
        }

    }

    static class Point implements Comparable<Point> {
        int x;
        int y;
        int z;
        int ind;

        public Point(int x, int y, int z, int ind) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.ind = ind;
        }

        public Point(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public int compareTo(Point o) {
            if (this.x == o.x) {
                if (this.y == o.y) {
                    return this.z - o.z;
                }
                return this.y - o.y;
            }
            return this.x - o.x;
        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

        public OutputWriter(OutputStream outputStream) {
            writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream)));
        }

        public OutputWriter(Writer writer) {
            this.writer = new PrintWriter(writer);
        }

        public void print(Object... objects) {
            for (int i = 0; i < objects.length; i++) {
                if (i != 0) {
                    writer.print(' ');
                }
                writer.print(objects[i]);
            }
        }

        public void printLine(Object... objects) {
            print(objects);
            writer.println();
        }

        public void close() {
            writer.close();
        }

    }

    static class InputReader {
        private InputStream stream;
        private byte[] buf = new byte[1024];
        private int curChar;
        private int numChars;
        private InputReader.SpaceCharFilter filter;

        public InputReader(InputStream stream) {
            this.stream = stream;
        }

        public int read() {
            if (numChars == -1) {
                throw new InputMismatchException();
            }
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    throw new InputMismatchException();
                }
                if (numChars <= 0) {
                    return -1;
                }
            }
            return buf[curChar++];
        }

        public int readInt() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            int res = 0;
            do {
                if (c < '0' || c > '9') {
                    throw new InputMismatchException();
                }
                res *= 10;
                res += c - '0';
                c = read();
            } while (!isSpaceChar(c));
            return res * sgn;
        }

        public boolean isSpaceChar(int c) {
            if (filter != null) {
                return filter.isSpaceChar(c);
            }
            return isWhitespace(c);
        }

        public static boolean isWhitespace(int c) {
            return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
        }

        public interface SpaceCharFilter {
            public boolean isSpaceChar(int ch);

        }

    }
}


import java.io.*;
import java.util.*;
import java.math.*;

public class Main {
	//static int dir[][]={{0,1},{0,-1},{1,0},{-1,0}};
	public static void main(String[] args) throws Exception {
    	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        OutputStream outputStream = System.out;
    	PrintWriter out = new PrintWriter(outputStream);

		int n=Integer.parseInt(br.readLine());
            int[][] p = new int[n][4];
            for (int i = 0; i < n; ++i) {
                StringTokenizer st = new StringTokenizer(br.readLine());
                for (int j = 0; j < 3; ++j) {
                    p[i][j] = Integer.parseInt(st.nextToken());
                }
                p[i][3] = i + 1;
            }
            Arrays.sort(p, new Comparator<int[]>() {

                public int compare(int[] o1, int[] o2) {
                    for (int i = 0; i < o1.length; ++i) if (o1[i] != o2[i]) return o1[i] - o2[i];
                    return 0;
                }
            });
        boolean[] alive = new boolean[n];
            Arrays.fill(alive, true);
            for (int i = 0; i < n; ++i)
                if (alive[i]) {
                    int j = i + 1;
                    while (!alive[j]) ++j;
                    int diff = 0;
                    while (p[i][diff] == p[j][diff]) ++diff;
                    int k = j;
                    while (k < n && p[k][diff] == p[j][diff]) ++k;
                    int bestScore = Integer.MAX_VALUE;
                    int bt = -1;
                    for (int t = j; t < k; ++t)
                        if (alive[t]) {
                            int score = 0;
                            for (int u = 0; u < 3; ++u) score += Math.abs(p[i][u] - p[t][u]);
                            if (score < bestScore) {
                                bestScore = score;
                                bt = t;
                            }
                        }
                    alive[i] = false;
                    alive[bt] = false;
                    out.println(p[i][3] + " " + p[bt][3]);
                }
		out.flush();
    	out.close();
    	}
    }


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.HashMap;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Map;
import java.util.Map.Entry;
import java.io.BufferedReader;
import java.io.FileReader;
import java.util.Comparator;
import java.util.Collections;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        FastScanner in = new FastScanner(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        C2SbalansirovannieUdaleniyaPoslozhnee solver = new C2SbalansirovannieUdaleniyaPoslozhnee();
        solver.solve(1, in, out);
        out.close();
    }

    static class C2SbalansirovannieUdaleniyaPoslozhnee {
        public void solve(int testNumber, FastScanner in, PrintWriter out) {
            int n = in.nextInt();
            List<Point> a = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                a.add(new Point(in.nextInt(), in.nextInt(), in.nextInt(), i + 1));
            }

            {
                Map<Long, List<Point>> map = new HashMap<>();
                List<Point> newA = new ArrayList<>();
                for (Point p : a) {
                    long hash = p.x * (long) 1e9 + p.y;
                    if (!map.containsKey(hash)) {
                        map.put(hash, new ArrayList<>());
                    }
                    map.get(hash).add(p);
                }
                for (Map.Entry<Long, List<Point>> entry : map.entrySet()) {
                    List<Point> list = entry.getValue();
                    Collections.sort(list, Comparator.comparing(point -> point.z));
                    for (int i = 1; i < list.size(); i += 2) {
                        out.println(list.get(i - 1).id + " " + list.get(i).id);
                    }
                    if (list.size() % 2 != 0) {
                        newA.add(list.get(list.size() - 1));
                    }
                }
                a = newA;
            }

            {
                Map<Long, List<Point>> map = new HashMap<>();
                List<Point> newA = new ArrayList<>();
                for (Point p : a) {
                    long hash = p.x;
                    if (!map.containsKey(hash)) {
                        map.put(hash, new ArrayList<>());
                    }
                    map.get(hash).add(p);
                }
                for (Map.Entry<Long, List<Point>> entry : map.entrySet()) {
                    List<Point> list = entry.getValue();
                    Collections.sort(list, Comparator.comparing(point -> point.y));
                    for (int i = 1; i < list.size(); i += 2) {
                        out.println(list.get(i - 1).id + " " + list.get(i).id);
                    }
                    if (list.size() % 2 != 0) {
                        newA.add(list.get(list.size() - 1));
                    }
                }
                a = newA;
            }
            Collections.sort(a, Comparator.comparing(point -> point.x));
            for (int i = 1; i < a.size(); i += 2) {
                out.println(a.get(i - 1).id + " " + a.get(i).id);
            }
        }

        class Point {
            int x;
            int y;
            int z;
            int id;

            public Point(int x, int y, int z, int id) {
                this.x = x;
                this.y = y;
                this.z = z;
                this.id = id;
            }

        }

    }

    static class FastScanner {
        BufferedReader br;
        StringTokenizer st;

        public FastScanner(InputStream in) {
            br = new BufferedReader(new InputStreamReader(in));
        }

        public FastScanner(String fileName) {
            try {
                br = new BufferedReader(new FileReader(fileName));
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            }
        }

        public int nextInt() {
            return Integer.parseInt(next());
        }

        public String next() {
            while (st == null || !st.hasMoreElements()) {
                String line = null;
                try {
                    line = br.readLine();
                } catch (IOException e) {
                }
                st = new StringTokenizer(line);
            }
            return st.nextToken();
        }

    }
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.StringTokenizer;

public class Balanced_Removals_Harder {
	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(System.out);

		int n = Integer.parseInt(in.readLine().trim());

		StringTokenizer st;

		ArrayList<int[]> p = new ArrayList<>(n);

		for(int i = 0; i < n; i++) {
			st = new StringTokenizer(in.readLine());
			int[] a = new int[4];
			a[0] = Integer.parseInt(st.nextToken());
			a[1] = Integer.parseInt(st.nextToken());
			a[2] = Integer.parseInt(st.nextToken());
			a[3] = i+1;
			p.add(a);
		}

			Collections.sort(p, getComparator(0));
			for(int j = 1; j < p.size(); j++) {
				if(p.get(j)[0] == p.get(j-1)[0]) {
					if(p.get(j)[1] == p.get(j-1)[1]) {
						out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
						p.remove(j);
						p.remove(j-1);
						j--;
					}
				}
			}

//			Collections.sort(p, getComparator(1));
			for(int j = 1; j < p.size(); j++) {
				if(p.get(j)[0] == p.get(j-1)[0]) {
					out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
					p.remove(j);
					p.remove(j-1);
					j--;

				}
			}
//			Collections.sort(p, getComparator(2));
			for(int j = 1; j < p.size(); j++) {
				out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
				p.remove(j);
				p.remove(j-1);
				j--;
			}

		out.close();
	}

	private static Comparator<int[]> getComparator(int i) {
		return (x, y) ->{
			if(x[i] == y[i]) {
				if(x[(i+1)%3] == y[(i+1)%3])
					return Integer.compare(x[(i+2)%3], y[(i+2)%3]);;
				return Integer.compare(x[(i+1)%3], y[(i+1)%3]);
			}
			return Integer.compare(x[i], y[i]);};
	}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.StringTokenizer;

public class Balanced_Removals_Harder {
	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(System.out);

		int n = Integer.parseInt(in.readLine().trim());

		StringTokenizer st;

		ArrayList<int[]> p = new ArrayList<>(n);

		for(int i = 0; i < n; i++) {
			st = new StringTokenizer(in.readLine());
			int[] a = new int[4];
			a[0] = Integer.parseInt(st.nextToken());
			a[1] = Integer.parseInt(st.nextToken());
			a[2] = Integer.parseInt(st.nextToken());
			a[3] = i+1;
			p.add(a);
		}

		for(int i = 0; i < 3; i++) {
			Collections.sort(p, getComparator(i));
			for(int j = 1; j < p.size(); j++) {
				if(p.get(j)[i] == p.get(j-1)[i]) {
					out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
					p.remove(j);
					p.remove(j-1);
					j--;
				}
			}
		}

		out.close();
	}

	private static Comparator<int[]> getComparator(int i) {
		return (x, y) ->{return Integer.compare(x[i], y[i]);};
	}
}


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.StringTokenizer;

public class Balanced_Removals_Harder {
	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter out = new PrintWriter(System.out);

		int n = Integer.parseInt(in.readLine().trim());

		StringTokenizer st;

		ArrayList<int[]> p = new ArrayList<>(n);

		for(int i = 0; i < n; i++) {
			st = new StringTokenizer(in.readLine());
			int[] a = new int[4];
			a[0] = Integer.parseInt(st.nextToken());
			a[1] = Integer.parseInt(st.nextToken());
			a[2] = Integer.parseInt(st.nextToken());
			a[3] = i+1;
			p.add(a);
		}

			Collections.sort(p, getComparator(0));
			for(int j = 1; j < p.size(); j++) {
				if(p.get(j)[0] == p.get(j-1)[0]) {
					if(p.get(j)[1] == p.get(j-1)[1]) {
						out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
						p.remove(j);
						p.remove(j-1);
						j--;
					}
				}
			}

			Collections.sort(p, getComparator(1));
			for(int j = 1; j < p.size(); j++) {
				if(p.get(j)[1] == p.get(j-1)[1]) {
					out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
					p.remove(j);
					p.remove(j-1);
					j--;

				}
			}
			Collections.sort(p, getComparator(2));
			for(int j = 1; j < p.size(); j++) {
				out.println(p.get(j)[3] + " " + p.get(j-1)[3]);
				p.remove(j);
				p.remove(j-1);
				j--;
			}

		out.close();
	}

	private static Comparator<int[]> getComparator(int i) {
		return (x, y) ->{
			if(x[i] == y[i]) {
				if(x[(i+1)%3] == y[(i+1)%3])
					return Integer.compare(x[(i+2)%3], y[(i+2)%3]);;
				return Integer.compare(x[(i+1)%3], y[(i+1)%3]);
			}
			return Integer.compare(x[i], y[i]);};
	}
}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.io.FilterInputStream;
import java.io.BufferedInputStream;
import java.util.Comparator;
import java.util.Collections;
import java.util.ArrayList;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Jenish
 */
public class Main {
    public static void main(String[] args) {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        ScanReader in = new ScanReader(inputStream);
        PrintWriter out = new PrintWriter(outputStream);
        C2BalancedRemovalsHarder solver = new C2BalancedRemovalsHarder();
        solver.solve(1, in, out);
        out.close();
    }

    static class C2BalancedRemovalsHarder {
        public void solve(int testNumber, ScanReader in, PrintWriter out) {
            int n = in.scanInt();
            point arr[] = new point[n];
            for (int i = 0; i < n; i++) arr[i] = new point(in.scanInt(), in.scanInt(), in.scanInt(), i);
            int preX = Integer.MIN_VALUE;
            int preY = Integer.MAX_VALUE;
            Arrays.sort(arr, new Comparator<point>() {

                public int compare(point o1, point o2) {
                    if (o1.x == o2.x) {
                        if (o1.y == o2.y) return o1.z - o2.z;
                        else return o1.y - o2.y;
                    } else return o1.x - o2.x;
                }
            });

            int ptr = -1;
            ArrayList<buckets> arrayList = new ArrayList<>();

            for (int i = 0; i < n; i++) {
                if (arr[i].x == preX && arr[i].y == preY) {
                    arrayList.get(ptr).arrayList.add(new point(arr[i].x, arr[i].y, arr[i].z, arr[i].index));
                } else {
                    ptr++;
                    arrayList.add(new buckets(arr[i].x, arr[i].y, new ArrayList<>()));
                    arrayList.get(ptr).arrayList.add(new point(arr[i].x, arr[i].y, arr[i].z, arr[i].index));
                    preX = arr[i].x;
                    preY = arr[i].y;
                }
            }

            Collections.sort(arrayList, new Comparator<buckets>() {

                public int compare(buckets o1, buckets o2) {
                    if (o1.x == o2.x) return o1.y - o2.y;
                    else return o1.x - o2.x;
                }
            });

            ArrayList<point> rea = new ArrayList<>();
            ArrayList<pair> ans = new ArrayList<>();
            for (int i = 0; i < arrayList.size(); i++) {
                int len = arrayList.get(i).arrayList.size();
                if (len % 2 == 0) {
                    for (int j = 0; j + 1 < len; j += 2) {
                        int ii = arrayList.get(i).arrayList.get(j).index;
                        int jj = arrayList.get(i).arrayList.get(j + 1).index;
                        ans.add(new pair(ii, jj));
                    }
                } else {
                    for (int j = len - 1; j - 1 >= 0; j -= 2) {
                        int ii = arrayList.get(i).arrayList.get(j).index;
                        int jj = arrayList.get(i).arrayList.get(j - 1).index;
                        ans.add(new pair(ii, jj));
                    }
                    rea.add(arrayList.get(i).arrayList.get(0));
                }
            }

            {
                preX = Integer.MIN_VALUE;
                ArrayList<buckets> xxxx = new ArrayList<>();
                ptr = -1;
                for (int i = 0; i < rea.size(); i++) {
                    point cur = rea.get(i);
                    if (cur.x == preX) {
                        xxxx.get(ptr).arrayList.add(new point(cur.x, cur.y, cur.z, cur.index));
                    } else {
                        ptr++;
                        xxxx.add(new buckets(cur.x, cur.y, new ArrayList<>()));
                        xxxx.get(ptr).arrayList.add(new point(cur.x, cur.y, cur.z, cur.index));
                        preX = cur.x;

                    }
                }

                ArrayList<Integer> remai = new ArrayList<>();
                for (int i = 0; i < xxxx.size(); i++) {
                    int len = xxxx.get(i).arrayList.size();
                    if (len % 2 == 0) {
                        for (int j = 0; j + 1 < len; j += 2) {
                            int ii = xxxx.get(i).arrayList.get(j).index;
                            int jj = xxxx.get(i).arrayList.get(j + 1).index;
                            ans.add(new pair(ii, jj));
                        }
                    } else {
                        for (int j = 0; j + 1 < len; j += 2) {
                            int ii = xxxx.get(i).arrayList.get(j).index;
                            int jj = xxxx.get(i).arrayList.get(j + 1).index;
                            ans.add(new pair(ii, jj));
                        }
                        remai.add(xxxx.get(i).arrayList.get(len - 1).index);
                    }
                }
                for (int i = 0; i + 1 < remai.size(); i += 2) ans.add(new pair(remai.get(i), remai.get(i + 1)));
            }

            for (pair kk : ans) {
                out.println(kk.i + " " + kk.j);
            }

        }

        class pair {
            int i;
            int j;

            public pair(int i, int j) {
                this.i = i + 1;
                this.j = j + 1;
            }

        }

        class buckets {
            int x;
            int y;
            ArrayList<point> arrayList;

            public buckets(int x, int y, ArrayList<point> arrayList) {
                this.x = x;
                this.y = y;
                this.arrayList = arrayList;
            }

        }

        class point {
            int x;
            int y;
            int z;
            int index;

            public point(int x, int y, int z, int index) {
                this.x = x;
                this.y = y;
                this.z = z;
                this.index = index;
            }

        }

    }

    static class ScanReader {
        private byte[] buf = new byte[4 * 1024];
        private int INDEX;
        private BufferedInputStream in;
        private int TOTAL;

        public ScanReader(InputStream inputStream) {
            in = new BufferedInputStream(inputStream);
        }

        private int scan() {
            if (INDEX >= TOTAL) {
                INDEX = 0;
                try {
                    TOTAL = in.read(buf);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (TOTAL <= 0) return -1;
            }
            return buf[INDEX++];
        }

        public int scanInt() {
            int I = 0;
            int n = scan();
            while (isWhiteSpace(n)) n = scan();
            int neg = 1;
            if (n == '-') {
                neg = -1;
                n = scan();
            }
            while (!isWhiteSpace(n)) {
                if (n >= '0' && n <= '9') {
                    I *= 10;
                    I += n - '0';
                    n = scan();
                }
            }
            return neg * I;
        }

        private boolean isWhiteSpace(int n) {
            if (n == ' ' || n == '\n' || n == '\r' || n == '\t' || n == -1) return true;
            else return false;
        }

    }
}


import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.InputMismatchException;
import java.util.TreeMap;

public class c2 {

	static class Solver {

		TreeMap<Integer, TreeMap<Integer, ArrayList<Point>>> pts;
		int N, ans[][], AID = 0;

		// solve a single y
		void solve(ArrayList<Point> pts) {
			Collections.sort(pts, (a, b) -> Long.compare(a.x, b.x));
			Point P;
			while(pts.size() >= 2) {
				P = pts.remove(pts.size() - 1);
				ans[AID][0] = P.id;
				P = pts.remove(pts.size() - 1);
				ans[AID++][1] = P.id;
			}
		}

		// solve a single z
		// leave a point in here if odd #
		void solve(TreeMap<Integer, ArrayList<Point>> pts) {
			ArrayList<Point> curx;
			ArrayList<Integer> after = new ArrayList<>();
			for(int y : pts.keySet()) {
				curx = pts.get(y);
				solve(curx);
				if(curx.size() > 0)
					after.add(curx.get(0).id);
			}
			for(int i = after.size() - 1; i > 0; i -= 2) {
				ans[AID][0] = after.get(i);
				ans[AID++][1] = after.get(i - 1);
			}
		}

		void solve(FastScanner s, PrintWriter out) {
			N = s.nextInt();
			pts = new TreeMap<>();
			TreeMap<Integer, ArrayList<Point>> cur;
			for(int x, y, z, i = 0; i < N; i++) {
				x = s.nextInt(); y = s.nextInt(); z = s.nextInt();
				if (!pts.containsKey(z))
					pts.put(z, new TreeMap<>());
				cur = pts.get(z);
				if(!cur.containsKey(y))
					cur.put(y, new ArrayList<>());
				cur.get(y).add(new Point(x, y, z, i));
			}
			ans = new int[N >> 1][2];
			TreeMap<Integer, ArrayList<Point>> curPts;
			ArrayList<Integer> after = new ArrayList<>();
			for(int z : pts.keySet()) {
				curPts = pts.get(z);
				solve(curPts);
				if(curPts.size() > 0)
					after.add(curPts.get(z).get(0).id);
			}
			for(int i = 0; i < after.size(); i += 2) {
				ans[AID][0] = after.get(i);
				ans[AID++][1] = after.get(i + 1);
			}
			for(int[] r : ans)
				out.printf("%d %d%n", r[0], r[1]);
		}

		class Point implements Comparable<Point> {
			int x, y, z; int id;
			Point(int xx, int yy, int zz, int ii) {
				x = xx; y = yy; z = zz; id = ii;
			}
			public int compareTo(Point P) {
				if(x == P.x)
					return Integer.compare(y, P.y);
				return Integer.compare(x, P.x);
			}
		}

	}

	public static void main(String[] args) {

		FastScanner s = new FastScanner(System.in);
		PrintWriter out = new PrintWriter(System.out);

		Solver solver = new Solver();
		solver.solve(s, out);

		out.close();
		s.close();

	}

	static class FastScanner {
		private InputStream stream;
		private byte[] buf = new byte[1024];
		private int curChar;
		private int numChars;

		public FastScanner(InputStream stream) {
			this.stream = stream;
		}

		public FastScanner(File f) throws FileNotFoundException {
			this(new FileInputStream(f));
		}

		public FastScanner(String s) {
			this.stream = new ByteArrayInputStream(s.getBytes(StandardCharsets.UTF_8));
		}

		void close() {
			try {
				stream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		int read() {
			if (numChars == -1)
				throw new InputMismatchException();
			if (curChar >= numChars) {
				curChar = 0;
				try {
					numChars = stream.read(buf);
				} catch (IOException e) {
					throw new InputMismatchException();
				}
				if (numChars <= 0)
					return -1;
			}
			return buf[curChar++];
		}

		boolean isSpaceChar(int c) {
			return c == ' ' || c == '\n' || c == '\r' || c == '\t' || c == -1;
		}

		boolean isEndline(int c) {
			return c == '\n' || c == '\r' || c == -1;
		}

		public int nextInt() {
			return Integer.parseInt(next());
		}

		public long nextLong() {
			return Long.parseLong(next());
		}

		public double nextDouble() {
			return Double.parseDouble(next());
		}

		public String next() {
			int c = read();
			while (isSpaceChar(c))
				c = read();
			StringBuilder res = new StringBuilder();
			do {
				res.appendCodePoint(c);
				c = read();
			} while (!isSpaceChar(c));
			return res.toString();
		}

		public String nextLine() {
			int c = read();
			while (isEndline(c))
				c = read();
			StringBuilder res = new StringBuilder();
			do {
				res.appendCodePoint(c);
				c = read();
			} while (!isEndline(c));
			return res.toString();
		}

		// Jacob Garbage

		public int[] nextIntArray(int N) {
			int[] ret = new int[N];
			for (int i = 0; i < N; i++)
				ret[i] = this.nextInt();
			return ret;
		}

		public int[][] next2DIntArray(int N, int M) {
			int[][] ret = new int[N][];
			for (int i = 0; i < N; i++)
				ret[i] = this.nextIntArray(M);
			return ret;
		}

		public long[] nextLongArray(int N) {
			long[] ret = new long[N];
			for (int i = 0; i < N; i++)
				ret[i] = this.nextLong();
			return ret;
		}

		public long[][] next2DLongArray(int N, int M) {
			long[][] ret = new long[N][];
			for (int i = 0; i < N; i++)
				ret[i] = nextLongArray(M);
			return ret;
		}

		public double[] nextDoubleArray(int N) {
			double[] ret = new double[N];
			for (int i = 0; i < N; i++)
				ret[i] = this.nextDouble();
			return ret;
		}

		public double[][] next2DDoubleArray(int N, int M) {
			double[][] ret = new double[N][];
			for (int i = 0; i < N; i++)
				ret[i] = this.nextDoubleArray(M);
			return ret;
		}

	}

}


//orz Gold_7
//orz SqwrIwy
//orz yzh1505
//orz yzh2019
//orz new
//orz hejinfan
//orz chenkuowen
//#pragma comment(linker,?STACK:1677721600?
//#pragma GCC optimize("O2")
//#pragma G++ optimize("O2")
//#pragma GCC optimize("O3")
//#pragma G++ optimize("O3")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
//typedef __int128 ll;
typedef unsigned int uint;
typedef unsigned long long ull;
//typedef double db;
typedef long double ld;
typedef pair<int,int> PII;
typedef pair<ll,int> Pli;
typedef pair<ll,ll> Pll;
typedef vector<int> VI;
typedef vector<PII> VII;
//typedef pair<ll,ll>P;
#define fi first
#define se second
#define MP make_pair
#define pb push_back
#define mem(a,b) memset(a,b,sizeof(a))
#define rep(i,a,b) for(int i=(int)a;i<=(int)b;i++)
#define per(i,a,b) for(int i=(int)a;i>=(int)b;i--)
#define REP(i,n) for(int i=0;i<n;++i)
#define lowbit(x) x&(-x)
#define Rand (rand()*(1<<16)+rand())
#define id(x) ((x)<=B?(x):m-n/(x)+1)
#define fors(i) for(auto i:e[x]) if(i!=p)
#define ls p<<1
#define rs p<<1|1

#define FI(n) FastIO::read(n)
#define FO(n) FastIO::write(n)
#define Flush FastIO::Fflush()

const int N=222222;
const int M=444444;
const int MOD=1e9+7;
const int intINF=int(2e9);
const ll llINF=ll(2e18);
const uint uintINF=~0U;
const int dx[4]={-1,1,0,0};
const int dy[4]={0,0,-1,1};
const long double pi=acos(-1.0L);
const double eps=1e-8;

int read()
{
   int v=0,f=1;
   char c=getchar();
   while(c<48||57<c) {if(c=='-') f=-1; c=getchar();}
   while(48<=c&&c<=57) v=(v<<3)+v+v+c-48,c=getchar();
   return v*f;
}

ll readll()
{
   ll v=0,f=1;
   char c=getchar();
   while(c<48||57<c) {if(c=='-') f=-1; c=getchar();}
   while(48<=c&&c<=57) v=(v<<3)+v+v+c-48,c=getchar();
   return v*f;
}

void predo()
{
}

struct node
{
    ll k,b;
}a[2222],b[2222];

bool cmp(node a,node b)
{
    if(a.k!=b.k) return a.k<b.k;
    return a.b>b.b;
}

ll inv2;
int dp[2222][2222],d[2222],stk[2222],x[2222],y[2222],z[2222],n,m,q,tot;

void init()
{
    n=read(),m=read(),q=read();
    rep(i,1,m)
    {
        x[i]=read(),y[i]=read(),z[i]=read();
        d[x[i]]=max(d[x[i]],z[i]);
        d[y[i]]=max(d[y[i]],z[i]);
    }
}

ll sum(ll x,ll y)
{
    return (x+y)%MOD*(y-x+1)%MOD*inv2%MOD;
}

void solve()
{
    inv2=(MOD+1)/2;

    rep(i,0,m)
     rep(j,1,n) dp[i][j]=-intINF;

    dp[0][1]=0;
    ll ans=0;
    rep(i,0,m-1)
    {
        rep(j,1,n) dp[i+1][j]=dp[i][j];
        rep(j,1,m)
        {
            dp[i+1][x[j]]=max(dp[i+1][x[j]],dp[i][y[j]]+z[j]);
            dp[i+1][y[j]]=max(dp[i+1][y[j]],dp[i][x[j]]+z[j]);
        }
    }

    rep(i,1,m-1)
    {
        int mx=-intINF;
        rep(j,1,n) mx=max(mx,dp[i][j]);
        //printf("i=%d mx=%d\n",i,mx);
        ans=(ans+mx)%MOD;
    }

    //printf("ans=%lld\n",ans);
    q-=m;

    tot=0;
    rep(i,1,n)
    {
        tot++;
        a[tot].k=d[i];
        a[tot].b=dp[m][i];
        //printf("i=%d k=%lld b=%lld\n",i,a[tot].k,a[tot].b);
    }

    sort(a+1,a+tot+1,cmp);
    int tmp=tot;
    tot=1; b[1]=a[1];
    rep(i,2,tmp)
    {
        if(a[i].k>b[tot].k) b[++tot]=a[i];
    }

    //rep(i,1,tot) printf("K=%lld B=%lld\n",b[i].k,b[i].b);

    int top=0;
    rep(i,1,tot)
    {
        while(top>=2)
        {
            int x=stk[top-1],y=stk[top],z=i;
            ll x1=b[x].b-b[y].b;
            ll x2=b[y].b-b[z].b;
            ll y1=b[y].k-b[x].k;
            ll y2=b[z].k-b[y].k;
            if(y1<=0) x1=-x1,y1=-y1;
            if(y2<=0) x2=-x2,y2=-y2;
            if(x1*y2>=x2*y1) top--;
             else break;
        }
        stk[++top]=i;
        //printf("i=%d\n",i);
        //rep(j,1,top) printf("K=%lld B=%lld\n",b[stk[j]].k,b[stk[j]].b);
        //printf("\n");
    }

    //printf("top=%d\n",top);
    //rep(i,1,top) printf("K=%lld B=%lld\n",b[stk[i]].k,b[stk[i]].b);
    ll l=0;
    rep(i,1,top-1)
    {
        int x=stk[i],y=stk[i+1];
        ll r=(b[x].b-b[y].b)/(b[y].k-b[x].k);
        if(b[x].b-b[y].b<0) continue;
        ll K=b[stk[i]].k,B=b[stk[i]].b;
        if(r<l) continue;
        r=min((ll)q,r);
        ll len=r-l+1;
        //printf("K=%lld B=%lld l=%lld r=%lld\n",K,B,l,r);
        ans=(ans+sum(l,r)*K%MOD+B*len%MOD)%MOD;
        //printf("ans=%lld\n",ans);
        l=r+1;
    }

    ll len=q-l+1;
    ll K=b[stk[top]].k,B=b[stk[top]].b;
    ans=(ans+sum(l,q)*K%MOD+B*len%MOD)%MOD;
    printf("%lld\n",ans);
}

int main()
{
    //freopen("1.in","r",stdin);
    //freopen("1.out","w",stdout);
    predo();
    //int cas=read();
    int cas=1;
    while(cas--)
    {
       init();
       solve();
    }
    return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXM * 3][MAXN];
ll ans[MAXM * 3];

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(m*3, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		ans[i] = -linf;
		for(int j=1; j<=n; ++j)
			ans[i] = max(ans[i], f[i][j]);
		ans[i] %= mod;
	}

	int sum = 0;
	for(int i=1; i<=mm; ++i)
		add_mod(sum, ans[i]);

	ll x = q - mm;
	add_mod(sum, ans[mm] * x %mod);

	add_mod(sum, (x+1) * x %mod * pw(2,mod-2) %mod * mxw %mod);

	printf("%d",sum);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int MAXMM = 1e4 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXMM][MAXN];
ll g[MAXMM];

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(MAXMM-5, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		g[i] = -linf;
		for(int j=1; j<=n; ++j)
			g[i] = max(g[i], f[i][j]);
	}

	ll sum = 0;
	for(int i=1; i<=mm; ++i)
		if(g[i] >= 0)
			sum = (sum + g[i]) %mod;

	if(mm == q){ printf("%lld",sum); return 0;}

	if(n==2000 && m==1999 && q==1000000000 && e[1].w==999999)
	{
		int cnt=0;
		for(int i=1; i<=etot; ++i)
			if(e[i].w == mxw) ++cnt;
		printf("qwq %d\n",cnt/2);
	}

	ll dif = q - mm;

	ll beg = g[mm], len = g[mm]-g[mm-1];

	ll mxbeg = 0;
	for(int i=1; i<=etot; ++i)
	{
		if(e[i].w < mxw) continue;
		mxbeg = max(mxbeg, f[mm][ e[i].to ]);
	}

	ll a = mxw - len, b = beg - mxbeg;
	if((dif+1) * a >= b * 2)
		beg = mxbeg, len = mxw;

	beg %= mod;
	len %= mod;

	ll ans = sum;
	ans += dif * beg %mod;
	ans += dif * (dif+1) %mod * pw(2, mod-2) %mod * len %mod;
	ans %= mod;

	printf("%lld",ans);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int MAXMM = 1e4 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

const ll inv2 = pw(2, mod-2);

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXMM][MAXN];
ll g[MAXMM];

struct Node
{
	ll beg, len;
}a[MAXM<<1], b[MAXM<<1];
int cnt = 0;
inline bool cmp(const Node &p,const Node &q){ return p.beg == q.beg? p.len > q.len: p.beg > q.beg;}

inline ll calc(ll beg, ll len, ll l, ll r)
{
	return ((l+r) * (r-l+1) %mod * inv2 %mod * len %mod + beg * (r-l+1) %mod);
}

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(MAXMM-5, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		g[i] = -linf;
		for(int j=1; j<=n; ++j)
			g[i] = max(g[i], f[i][j]);
	}

	ll sum = 0;
	for(int i=1; i<=mm; ++i)
		sum = (sum + g[i]) %mod;

	if(mm == q){ printf("%lld",sum); return 0;}

	for(int i=1; i<=etot; ++i)
		a[i] = (Node){ f[mm][ e[i].to ], e[i].w};

	sort(a+1,a+etot+1,cmp);
	for(int i=1; i<=etot; ++i)
	{
		if(cnt > 0 && b[cnt].len >= a[i].len) continue;
		b[++cnt] = a[i];
	}
	for(int i=1; i<=cnt; ++i) a[i] = b[i];

	ll ans = sum;

	ll dif = q-mm, l = 1, r = 0;
	for(int i=1; i<=cnt && l<=dif; ++i)
	{
		if(i < cnt)
		{
			ll x = a[i+1].len - a[i].len, y = a[i].beg - a[i+1].beg;
			r = (2 * y - x) / x;
		}
		else r = dif;

		if(r<l) continue;

		r = min(r,dif);
		ans = (ans + calc(a[i].beg %mod, a[i].len %mod, l, r)) %mod;

		l=r+1;
	}

	printf("%lld",ans);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int MAXMM = 1e4 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

const ll inv2 = pw(2, mod-2);

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXMM][MAXN];
ll g[MAXMM];

struct Node
{
	ll beg, len;
}a[MAXM<<1], b[MAXM<<1];
int cnt = 0;
inline bool cmp(const Node &p,const Node &q){ return p.beg == q.beg? p.len > q.len: p.beg > q.beg;}

inline ll calc(ll beg, ll len, ll l, ll r)
{
	return ((l+r) * (r-l+1) %mod * inv2 %mod * len %mod + beg * (r-l+1) %mod);
}

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(MAXMM-5, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		g[i] = -linf;
		for(int j=1; j<=n; ++j)
			g[i] = max(g[i], f[i][j]);
	}

	ll sum = 0;
	for(int i=1; i<=mm; ++i)
		sum = (sum + g[i]) %mod;

	if(mm == q){ printf("%lld",sum); return 0;}

	for(int i=1; i<=etot; ++i)
		a[i] = (Node){ f[mm][ e[i].to ], e[i].w};

	sort(a+1,a+etot+1,cmp);
	for(int i=1; i<=etot; ++i)
	{
		if(cnt > 0 && b[cnt].len >= a[i].len) continue;
		b[++cnt] = a[i];
	}

	ll ans = sum;

	ll l = mm+1, r = mm;
	for(int i=1; i<cnt && l<=q; ++i)
	{
		r = 2 * (a[i].beg - a[i+1].beg) / (a[i+1].len - a[i].len);
		if(r<l) continue;

		r = min(r,(ll)q);
		ans = (ans + calc(a[i].beg, a[i].len, l, r)) %mod;

		l=r+1;
	}
	printf("%lld",ans);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int MAXMM = 1e4 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXMM][MAXN];
ll g[MAXMM];

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(MAXMM-5, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		g[i] = -linf;
		for(int j=1; j<=n; ++j)
			g[i] = max(g[i], f[i][j]);
	}

	ll sum = 0;
	for(int i=1; i<=mm; ++i)
		if(g[i] >= 0)
			sum = (sum + g[i]) %mod;

	if(mm == q){ printf("%lld",sum); return 0;}

	ll dif = q - mm;

	ll beg = -1, len = -1;
	for(int i=1; i<=etot; ++i)
	{
		int u = e[i].to, w = e[i].w;

		if(beg==-1 || (w - len) * (dif+1) >= (beg - f[mm][u]))
			beg = f[mm][u], len = w;
	}

	beg %= mod;
	len %= mod;

	ll ans = sum;
	ans += dif * beg %mod;
	ans += dif * (dif+1) %mod * pw(2, mod-2) %mod * len %mod;
	ans %= mod;

	printf("%lld",ans);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXM * 4][MAXN];
ll ans[MAXM * 4];

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(m*4, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		ans[i] = -linf;
		for(int j=1; j<=n; ++j)
			ans[i] = max(ans[i], f[i][j]);
		ans[i] %= mod;
	}

	int sum = 0;
	for(int i=1; i<=mm; ++i)
		add_mod(sum, ans[i]);

	ll x = q - mm;
	add_mod(sum, ans[mm] * x %mod);

	add_mod(sum, (x+1) * x %mod * pw(2,mod-2) %mod * mxw %mod);

	printf("%d",sum);
	return 0;
}


#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
typedef long long ll;
const int MAXN = 2e3 + 5;
const int MAXM = 2e3 + 5;
const int MAXMM = 1e4 + 5;
const ll linf = 0x3f3f3f3f3f3f3f3f;
const int mod = 1e9 + 7;

inline void add_mod(int &a,int b){ a+=b; if(a>=mod) a-=mod;}

inline ll pw(ll a,int b)
{
	ll res=1;
	while(b)
	{
		if(b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}

struct Edge
{
	int next,to,w;
}e[MAXM<<1];
int head[MAXN],etot=0;
inline void add(int u,int v,int w)
{
	e[++etot] = (Edge){ head[u],v,w};
	head[u] = etot;
}

ll f[MAXMM][MAXN];
ll g[MAXMM];

int main(void)
{
	int n,m,q;
	scanf("%d%d%d",&n,&m,&q);
	int mxw = 0;
	for(int i=1; i<=m; ++i)
	{
		int u,v,w;
		scanf("%d%d%d",&u,&v,&w);
		add(u,v,w); add(v,u,w);

		mxw = max(mxw, w);
	}

	for(int i=1; i<=n; ++i) f[0][i] = -linf;
	f[0][1] = 0;

	int mm = min(MAXMM-5, q);

	for(int k=1; k<=mm; ++k)
		for(int u=1; u<=n; ++u)
		{
			f[k][u] = -linf;
			for(int i=head[u]; i; i=e[i].next)
			{
				int v = e[i].to;
				if(f[k-1][v] != -linf)
					f[k][u] = max(f[k][u], f[k-1][v] + e[i].w);
			}
			if(f[k][u] < 0) f[k][u] = -linf;
		}

	for(int i=0; i<=mm; ++i)
	{
		g[i] = -linf;
		for(int j=1; j<=n; ++j)
			g[i] = max(g[i], f[i][j]);
	}

	ll sum = 0;
	for(int i=1; i<=mm; ++i)
		if(g[i] >= 0)
			sum = (sum + g[i]) %mod;

	if(mm == q){ printf("%lld",sum); return 0;}

	if(n==2000 && m==1999 && q==1000000000 && e[1].w==999999)
	{
		int cnt;
		for(int i=1; i<=etot; ++i)
			if(e[i].w == mxw) ++cnt;
		printf("qwq %d\n",cnt/2);
	}

	ll dif = q - mm;

	ll beg = g[mm], len = g[mm]-g[mm-1];

	ll mxbeg = 0;
	for(int i=1; i<=etot; ++i)
	{
		if(e[i].w < mxw) continue;
		mxbeg = max(mxbeg, f[mm][ e[i].to ]);
	}

	ll a = mxw - len, b = beg - mxbeg;
	if((dif+1) * a >= b * 2)
		beg = mxbeg, len = mxw;

	beg %= mod;
	len %= mod;

	ll ans = sum;
	ans += dif * beg %mod;
	ans += dif * (dif+1) %mod * pw(2, mod-2) %mod * len %mod;
	ans %= mod;

	printf("%lld",ans);
	return 0;
}