This is a harder version of the problem. In this version, n ≤ 50 000.

There are n distinct points in three-dimensional space numbered from 1 to n. The i-th point has coordinates (x_i, y_i, z_i). The number of points n is even.

You'd like to remove all n points using a sequence of n/2 snaps. In one snap, you can remove any two points a and b that have not been removed yet and form a perfectly balanced pair. A pair of points a and b is perfectly balanced if no other point c (that has not been removed yet) lies within the axis-aligned minimum bounding box of points a and b.

Formally, point c lies within the axis-aligned minimum bounding box of points a and b if and only if min(x_a, x_b) ≤ x_c ≤ max(x_a, x_b), min(y_a, y_b) ≤ y_c ≤ max(y_a, y_b), and min(z_a, z_b) ≤ z_c ≤ max(z_a, z_b). Note that the bounding box might be degenerate. 

Find a way to remove all points in n/2 snaps.

Input

The first line contains a single integer n (2 ≤ n ≤ 50 000; n is even), denoting the number of points.

Each of the next n lines contains three integers x_i, y_i, z_i (-10^8 ≤ x_i, y_i, z_i ≤ 10^8), denoting the coordinates of the i-th point.

No two points coincide.

Output

Output n/2 pairs of integers a_i, b_i (1 ≤ a_i, b_i ≤ n), denoting the indices of points removed on snap i. Every integer between 1 and n, inclusive, must appear in your output exactly once.

We can show that it is always possible to remove all points. If there are many solutions, output any of them.

Examples

Input


6
3 1 0
0 3 0
2 2 0
1 0 0
1 3 0
0 1 0


Output


3 6
5 1
2 4


Input


8
0 1 1
1 0 1
1 1 0
1 1 1
2 2 2
3 2 2
2 3 2
2 2 3


Output


4 5
1 6
2 7
3 8

Note

In the first example, here is what points and their corresponding bounding boxes look like (drawn in two dimensions for simplicity, as all points lie on z = 0 plane). Note that order of removing matters: for example, points 5 and 1 don't form a perfectly balanced pair initially, but they do after point 3 is removed. 

<image>
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.*;
import java.io.*;
import java.math.*;
public class Main {
    InputStream is;
    PrintWriter out;
    String INPUT = "";
    //class  Declaration

    static class pair implements Comparable < pair > {
        long x;
        long y;

        
        pair(long i, long j) {
            x = i;
            y = j;
            


        }
        public int compareTo(pair p) {
            if (this.x != p.x) {
                return Long.compare(this.x,p.x);
            } else {
                return Long.compare(this.y,p.y);
            }
        }
        public int hashCode() {
            return (x + " " + y).hashCode();
        }
        public String toString() {
            return x + " " + y;
        }
        public boolean equals(Object o) {
            pair x = (pair) o;
            return (x.x == this.x && x.y == this.y);
        }
    }
    // int[] dx = {0,0,1,-1};
    // int[] dy = {1,-1,0,0};
    // int[] ddx = {0,0,1,-1,1,-1,1,-1}; 
    // int[] ddy = {1,-1,0,0,1,-1,-1,1};

int inf = (int) 1e9 + 9;
long biginf  = (long)1e17 + 7 ;
long mod =  (long)1e9 + 7;  


void solve() throws Exception {
int n=ni();
Long[][] points = new Long[n][3];
TreeMap<Long,TreeMap<Long,TreeMap<Long,Integer>>> coord =new TreeMap<>();
boolean used[] = new boolean[n];
for(int i=0;i<n;++i){
    points[i][0] = nl();points[i][1] = nl();points[i][2] = nl();
    //final long[] fpoints = {points[i][0],points[i][1],points[i][1]};
    final int ind = i ;
    
    coord.compute(points[ind][2],(key,val)->{
        if(val==null){
            TreeMap<Long,TreeMap<Long,Integer>> ycor = new TreeMap<>();
            TreeMap<Long,Integer> xcor = new TreeMap<>();
            xcor.put(points[ind][0],ind+1);
            ycor.put(points[ind][1],xcor);
            return ycor;
        }
        else{

            TreeMap<Long,TreeMap<Long,Integer>> ycor= val ;
            ycor.compute(points[ind][1],(kk,xcor)->{
                if(xcor==null){
                    TreeMap<Long,Integer> xx = new TreeMap<>();
                    xx.put(points[ind][0],ind+1);
                    return xx;
                }
                else{
                    TreeMap<Long,Integer> xx = xcor;
                    xx.put(points[ind][0],ind+1);
                    return xx ;
                }
            });
            return ycor;
        }
    });
}
//pn(coord);

int[] ans = new int[n];
int ians = 0;
ArrayList<Integer> zans= new ArrayList<>();
for(long z : coord.keySet()){
    TreeMap<Long,TreeMap<Long,Integer>> ycor = coord.get(z);   
    ArrayList<Integer> yans= new ArrayList<>();
    for(long y: ycor.keySet()){
        TreeMap<Long,Integer> xcor = ycor.get(y);
        ArrayList<Integer> xans = new ArrayList<>();
        for(long x : xcor.keySet()){
            //ans[ians++] = xcor.get(x);
            xans.add(xcor.get(x));
        }
        for(int i=0;i<=xans.size()-2;i+=2){
            pn(xans.get(i)+" "+xans.get(i+1));
        }
        if(xans.size()%2==1){
            yans.add(xans.get(xans.size()-1));
        }
        
        
    }
    for(int i=0;i<=yans.size()-2;i+=2){
        pn(yans.get(i)+" "+yans.get(i+1));
    }
    if(yans.size()%2==1){
        zans.add(yans.get(yans.size()-1));
    }

}
for(int i=0;i<=zans.size()-2;i+=2){
    pn(zans.get(i)+" "+zans.get(i+1));
}




//pn(coord);

//pn(Arrays.deepToString(points));


}
long dist(Long[] a, Long[]b){
    long dis = 0;
    for(int i=0;i<3;++i){
        dis+= (a[i]-b[i])*(a[i]-b[i]);
    }
    return dis;
}

long pow(long a, long b) {
        long result = 1;
        while (b > 0) {
            if (b % 2 == 1) result = (result * a) % mod;
            b /= 2;
            a = (a * a) % mod;
        }
        return result;
    }


    void print(Object o) {
        System.out.println(o);
        System.out.flush();
    }



    long gcd(long a, long b) {
        if (b == 0)
            return a;
        return gcd(b, a % b);
    }
    void run() throws Exception {
        is = INPUT.isEmpty() ? System.in : new ByteArrayInputStream(INPUT.getBytes());
        out = new PrintWriter(System.out);

        long s = System.currentTimeMillis();
        solve();
        out.flush();
        if (!INPUT.isEmpty()) tr(System.currentTimeMillis() - s + "ms");
    }


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

    //output methods
    private void pn(Object o) {
        out.println(o);
    }
    private void p(Object o) {
        out.print(o);
    }
    private ArrayList < ArrayList < Integer >> ng(int n, int e) {
        ArrayList < ArrayList < Integer >> g = new ArrayList < > ();
        for (int i = 0; i <= n; ++i) g.add(new ArrayList < > ());

        for (int i = 0; i < e; ++i) {
            int u = ni(), v = ni();
            g.get(u).add(v);
            g.get(v).add(u);
        }

        return g;
    }

    private ArrayList < ArrayList < pair >> nwg(int n, int e) {
        ArrayList < ArrayList < pair >> g = new ArrayList < > ();
        for (int i = 0; i <= n; ++i) g.add(new ArrayList < > ());

        for (int i = 0; i < e; ++i) {
            int u = ni(), v = ni(), w = ni();
            g.get(u).add(new pair(w, v));
            g.get(v).add(new pair(w, u));
        }

        return g;
    }


    //input methods
    private byte[] inbuf = new byte[1024];
    public int lenbuf = 0, ptrbuf = 0;


    private int readByte() {
        if (lenbuf == -1) throw new InputMismatchException();
        if (ptrbuf >= lenbuf) {
            ptrbuf = 0;
            try {
                lenbuf = is.read(inbuf);
            } catch (IOException e) {
                throw new InputMismatchException();
            }
            if (lenbuf <= 0) return -1;
        }
        return inbuf[ptrbuf++];
    }




    private boolean isSpaceChar(int c) {
        return !(c >= 33 && c <= 126);
    }
    private int skip() {
        int b;
        while ((b = readByte()) != -1 && isSpaceChar(b));
        return b;
    }

    private double nd() {
        return Double.parseDouble(ns());
    }
    private char nc() {
        return (char) skip();
    }

    private String ns() {
        int b = skip();
        StringBuilder sb = new StringBuilder();
        while (!(isSpaceChar(b))) { // when nextLine, (isSpaceChar(b) && b != ' ')
            sb.appendCodePoint(b);
            b = readByte();
        }
        return sb.toString();
    }

    private char[] ns(int n) {
        char[] buf = new char[n];
        int b = skip(), p = 0;
        while (p < n && !(isSpaceChar(b))) {
            buf[p++] = (char) b;
            b = readByte();
        }
        return n == p ? buf : Arrays.copyOf(buf, p);
    }

    private char[][] nm(int n, int m) {
        char[][] map = new char[n][];
        for (int i = 0; i < n; i++) map[i] = ns(m);
        return map;
    }

    private int[] na(int n) {
        int[] a = new int[n];
        for (int i = 0; i < n; i++) a[i] = ni();
        return a;
    }

    private int ni() {
        int num = 0, b;
        boolean minus = false;
        while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
        if (b == '-') {
            minus = true;
            b = readByte();
        }

        while (true) {
            if (b >= '0' && b <= '9') {
                num = num * 10 + (b - '0');
            } else {
                return minus ? -num : num;
            }
            b = readByte();
        }
    }

    private long nl() {
        long num = 0;
        int b;
        boolean minus = false;
        while ((b = readByte()) != -1 && !((b >= '0' && b <= '9') || b == '-'));
        if (b == '-') {
            minus = true;
            b = readByte();
        }

        while (true) {
            if (b >= '0' && b <= '9') {
                num = num * 10 + (b - '0');
            } else {
                return minus ? -num : num;
            }
            b = readByte();
        }
    }

    private void tr(Object...o) {
        if (INPUT.length() > 0) System.out.println(Arrays.deepToString(o));
    }
    void watch(Object...a) throws Exception {
        int i = 1;
        print("watch starts :");
        for (Object o: a) {
            //print(o);
            boolean notfound = true;
            if (o.getClass().isArray()) {

                String type = o.getClass().getName().toString();
                //print("type is "+type);
                switch (type) {
                    case "[I":
                        {
                            int[] test = (int[]) o;
                            print(i + " " + Arrays.toString(test));
                            break;
                        }
                    case "[[I":
                        {
                            int[][] obj = (int[][]) o;

                            print(i + " " + Arrays.deepToString(obj));
                            break;
                        }
                    case "[J":
                        {

                            long[] obj = (long[]) o;
                            print(i + " " + Arrays.toString(obj));
                            break;
                        }
                    case "[[J":
                        {

                            long[][] obj = (long[][]) o;
                            print(i + " " + Arrays.deepToString(obj));
                            break;
                        }
                    case "[D":
                        {

                            double[] obj = (double[]) o;
                            print(i + " " + Arrays.toString(obj));
                            break;
                        }
                    case "[[D":
                        {

                            double[][] obj = (double[][]) o;
                            print(i + " " + Arrays.deepToString(obj));
                            break;
                        }
                    case "[Ljava.lang.String":
                        {

                            String[] obj = (String[]) o;
                            print(i + " " + Arrays.toString(obj));
                            break;
                        }
                    case "[[Ljava.lang.String":
                        {

                            String[][] obj = (String[][]) o;
                            print(i + " " + Arrays.deepToString(obj));
                            break;
                        }
                    case "[C":
                        {
                            char[] obj = (char[]) o;
                            print(i + " " + Arrays.toString(obj));
                            break;
                        }
                    case "[[C":
                        {

                            char[][] obj = (char[][]) o;
                            print(i + " " + Arrays.deepToString(obj));
                            break;
                        }



                    default:
                        {
                            print(i + " type not identified");
                            break;
                        }
                }
                notfound = false;

            }
            if (o.getClass() == ArrayList.class) {
                print(i + " al: " + o);
                notfound = false;
            }
            if (o.getClass() == HashSet.class) {
                print(i + " hs: " + o);
                notfound = false;
            }
            if (o.getClass() == TreeSet.class) {
                print(i + " ts: " + o);
                notfound = false;
            }
            if (o.getClass() == TreeMap.class) {
                print(i + " tm: " + o);
                notfound = false;
            }
            if (o.getClass() == HashMap.class) {
                print(i + " hm: " + o);
                notfound = false;
            }
            if (o.getClass() == LinkedList.class) {
                print(i + " ll: " + o);
                notfound = false;
            }
            if (o.getClass() == PriorityQueue.class) {
                print(i + " pq : " + o);
                notfound = false;
            }
            if (o.getClass() == pair.class) {
                print(i + " pq : " + o);
                notfound = false;
            }

            if (notfound) {
                print(i + " unknown: " + o);
            }
            i++;
        }
        print("watch ends ");
    }

}