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.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.BufferedWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeMap;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.Comparator;
import java.util.Collections;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author Ben King
 */
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);
        C2BalancedRemovalsHarder solver = new C2BalancedRemovalsHarder();
        solver.solve(1, in, out);
        out.close();
    }

    static class C2BalancedRemovalsHarder {
        int[][] points;

        public void solve(int testNumber, InputReader in, OutputWriter out) {
            int N = in.nextInt();
            points = new int[N][3];
            for (int i = 0; i < N; i++) {
                points[i][0] = in.nextInt();
                points[i][1] = in.nextInt();
                points[i][2] = in.nextInt();
            }

            TreeMap<Integer, Plane> planes = new TreeMap<>();

            for (int p = 0; p < N; p++) {
                if (!planes.containsKey(points[p][0])) {
                    planes.put(points[p][0], new Plane(points[p][0], out));
                }
                planes.get(points[p][0]).insert(p);
            }

            int carry = -1;
            for (Plane plane : planes.values()) {
//            out.println("process: " + plane.xval);
                plane.sort();
                int x = plane.solve();
                if (x != -1) {
                    if (carry != -1) {
                        out.println(carry + 1 + " " + (x + 1));
                        carry = -1;
                    }
                    else {
                        carry = x;
                    }
                }
            }


            out.flush();
        }

        class Plane {
            OutputWriter out;
            int xval;
            TreeMap<Integer, List<Integer>> lines = new TreeMap<>();

            public Plane(int xval, OutputWriter out) {
                this.xval = xval;
                this.out = out;
            }

            public void insert(int point) {
                if (!lines.containsKey(points[point][1])) {
                    lines.put(points[point][1], new ArrayList<>());
                }
                lines.get(points[point][1]).add(point);
            }

            public void sort() {
                for (List<Integer> list : lines.values()) {
                    Collections.sort(list, new Comparator<Integer>() {

                        public int compare(Integer o1, Integer o2) {
                            return Integer.compare(points[o1][2], points[o2][2]);
                        }
                    });
                }
            }

            public int solve() {
                int carry = -1;
                for (List<Integer> line : lines.values()) {
                    for (int i = 0; i < line.size(); i += 2) {
                        if (i + 1 < line.size()) {
                            out.println((line.get(i) + 1) + " " + (line.get(i + 1) + 1));
                        }
                        else if (carry != -1) {
                            out.println((carry + 1) + " " + (line.get(i) + 1));
                            carry = -1;
                        }
                        else {
                            carry = line.get(i);
                        }
                    }
                }
                return carry;
            }

        }

    }

    static class OutputWriter {
        private final PrintWriter writer;

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

        public OutputWriter(java.io.Writer outputStreamWriter) {
            writer = new PrintWriter(new BufferedWriter(outputStreamWriter));
        }

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

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

        public void println(String s) {
            writer.println(s);
        }

    }

    static class InputReader {
        private static final int DEFAULT_BUFFER_SIZE = 1 << 16;
        private static final InputStream DEFAULT_STREAM = System.in;
        private int c;
        private byte[] buf;
        private int bufferSize;
        private int bufIndex;
        private int numBytesRead;
        private InputStream stream;
        private static final byte EOF = -1;
        private static final byte SPACE = 32;
        private static final byte DASH = 45;
        private char[] charBuffer;
        private static byte[] bytes = new byte[58];
        private static int[] ints = new int[58];
        private static char[] chars = new char[128];

        static {
            char ch = ' ';
            int value = 0;
            byte _byte = 0;
            for (int i = 48; i < 58; i++) bytes[i] = _byte++;
            for (int i = 48; i < 58; i++) ints[i] = value++;
            for (int i = 32; i < 128; i++) chars[i] = ch++;
        }

        public InputReader() {
            this(DEFAULT_STREAM, DEFAULT_BUFFER_SIZE);
        }

        public InputReader(int bufferSize) {
            this(DEFAULT_STREAM, bufferSize);
        }

        public InputReader(InputStream stream) {
            this(stream, DEFAULT_BUFFER_SIZE);
        }

        public InputReader(InputStream stream, int bufferSize) {
            if (stream == null || bufferSize <= 0)
                throw new IllegalArgumentException();
            buf = new byte[bufferSize];
            charBuffer = new char[128];
            this.bufferSize = bufferSize;
            this.stream = stream;
        }

        private int readJunk(int token) {

            if (numBytesRead == EOF) return EOF;

            // Seek to the first valid position index
            do {

                while (bufIndex < numBytesRead) {
                    if (buf[bufIndex] > token) return 0;
                    bufIndex++;
                }

                // reload buffer
                try {
                    numBytesRead = stream.read(buf);
                }
                catch (IOException e) {
                    e.printStackTrace();
                }
                if (numBytesRead == EOF) return EOF;
                bufIndex = 0;

            } while (true);

        }

        public int nextInt() {
            try {
                if (readJunk(DASH - 1) == EOF) throw new IOException();
                int sgn = 1, res = 0;

                c = buf[bufIndex];
                if (c == DASH) {
                    sgn = -1;
                    bufIndex++;
                }

                do {

                    while (bufIndex < numBytesRead) {
                        if (buf[bufIndex] > SPACE) {
                            res = (res << 3) + (res << 1);
                            res += ints[buf[bufIndex++]];
                        }
                        else {
                            bufIndex++;
                            return res * sgn;
                        }
                    }

                    // Reload buffer
                    numBytesRead = stream.read(buf);
                    if (numBytesRead == EOF) return res * sgn;
                    bufIndex = 0;

                } while (true);
            }
            catch (IOException e) {
                e.printStackTrace();
                return -1;
            }

        }

    }
}

