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;
            }

        }

    }
}


/**
 * BaZ :D
 */

import java.lang.reflect.Array;
import java.util.*;
import java.io.*;
import static java.lang.Math.*;

public class ACMIND
{
    static FastReader scan;
    static PrintWriter pw;
    static long MOD = 1_000_000_007;
    static long INF = 1_000_000_000_000_000_000L;
    static long inf = 2_000_000_000;
    public static void main(String[] args) {
        new Thread(null,null,"BaZ",1<<27)
        {
            public void run()
            {
                try
                {
                    solve();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }.start();
    }

    static void solve() throws IOException
    {
        scan = new FastReader();
        pw = new PrintWriter(System.out,true);
        StringBuilder sb = new StringBuilder();
        int n = ni();
        TreeMap<Integer, LinkedList<Tuple>> map = new TreeMap<>();
        for(int i=0;i<n;++i) {
            int x = ni(), y  = ni(), z = ni();
            LinkedList<Tuple> ll = map.get(x);
            if(ll==null) {
                ll = new LinkedList<>();
            }
            ll.addFirst(new Tuple(y,z,i+1));
            map.put(x, ll);
        }
        for(int e : map.keySet()) {
            Collections.sort(map.get(e));
        }
        for(Map.Entry<Integer, LinkedList<Tuple>> e : map.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            while(ll.size()>1) {
                pl(ll.pollFirst().idx+ " " + ll.pollFirst().idx);
            }
        }
        ArrayList<Integer> rem = new ArrayList<>();
        for(Map.Entry<Integer, LinkedList<Tuple>> e : map.entrySet()) {
            if(e.getValue().size()>0) {
                rem.add(e.getKey());
            }
        }
        //pa("rem", rem);
        Collections.sort(rem);
        for(int i=0;i<rem.size();i+=2) {
            pl(map.get(rem.get(i)).pollFirst().idx + " "+ map.get(rem.get(i+1)).pollFirst().idx);
        }
        pw.flush();
        pw.close();
    }
    static class Tuple implements Comparable<Tuple> {
        int y,z,idx;
        Tuple(int y, int z, int idx) {
            this.y = y;
            this.z = z;
            this.idx = idx;
        }
        public int compareTo(Tuple o) {
            if(this.y!=o.y) {
                return this.y - o.y;
            }
            return this.z - o.z;
        }
    }
    static int ni() throws IOException
    {
        return scan.nextInt();
    }
    static long nl() throws IOException
    {
        return scan.nextLong();
    }
    static double nd() throws IOException
    {
        return scan.nextDouble();
    }
    static void pl()
    {
        pw.println();
    }
    static void p(Object o)
    {
        pw.print(o+" ");
    }
    static void pl(Object o)
    {
        pw.println(o);
    }
    static void psb(StringBuilder sb)
    {
        pw.print(sb);
    }
    static void pa(String arrayName, Object arr[])
    {
        pl(arrayName+" : ");
        for(Object o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, int arr[])
    {
        pl(arrayName+" : ");
        for(int o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, long arr[])
    {
        pl(arrayName+" : ");
        for(long o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, double arr[])
    {
        pl(arrayName+" : ");
        for(double o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, char arr[])
    {
        pl(arrayName+" : ");
        for(char o : arr)
            p(o);
        pl();
    }
    static void pa(String listName, List list)
    {
        pl(listName+" : ");
        for(Object o : list)
            p(o);
        pl();
    }
    static void pa(String arrayName, Object[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(Object o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, int[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(int o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, long[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(long o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, char[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(char o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, double[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(double o : arr[i])
                p(o);
            pl();
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
}


/**
 * BaZ :D
 */

import java.lang.reflect.Array;
import java.util.*;
import java.io.*;
import static java.lang.Math.*;

public class ACMIND
{
    static FastReader scan;
    static PrintWriter pw;
    static long MOD = 1_000_000_007;
    static long INF = 1_000_000_000_000_000_000L;
    static long inf = 2_000_000_000;
    public static void main(String[] args) {
        new Thread(null,null,"BaZ",1<<27)
        {
            public void run()
            {
                try
                {
                    solve();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }.start();
    }

    static void solve() throws IOException
    {
        scan = new FastReader();
        pw = new PrintWriter(System.out,true);
        StringBuilder sb = new StringBuilder();
        int n = ni();
        TreeMap<Integer, TreeMap<Integer, LinkedList<Tuple>>> map = new TreeMap<>();
        for(int i=0;i<n;++i) {
            int x = ni(), y  = ni(), z = ni();
            TreeMap<Integer, LinkedList<Tuple>> ll = map.get(x);
            if(ll==null) {
                ll = new TreeMap<>();
            }
            LinkedList<Tuple> lll = ll.get(y);
            if(lll == null) {
                lll = new LinkedList<>();
            }
            lll.addLast(new Tuple(z, i+1));
            ll.put(y, lll);
            map.put(x, ll);
        }
        for(Map.Entry<Integer, TreeMap<Integer, LinkedList<Tuple>>> e : map.entrySet()) {
            Map<Integer, LinkedList<Tuple>> mapy = e.getValue();
            for(Map.Entry<Integer, LinkedList<Tuple>> f : mapy.entrySet()) {
                LinkedList<Tuple> ll = f.getValue();
                Collections.sort(ll);
                //pa("ll", ll);
                while(ll.size()>1) {
                    pl(ll.pollFirst().idx + " " + ll.pollFirst().idx);
                }
            }
        }
        TreeMap<Integer, LinkedList<Tuple>> reduced = new TreeMap<>();
        for(Map.Entry<Integer, TreeMap<Integer, LinkedList<Tuple>>> e : map.entrySet()) {
            Map<Integer, LinkedList<Tuple>> mapy = e.getValue();
            for(Map.Entry<Integer, LinkedList<Tuple>> f : mapy.entrySet()) {
                LinkedList<Tuple> ll = f.getValue();
                if(ll.size()>0) {
                    LinkedList<Tuple> lol = reduced.get(e.getKey());
                    if(lol==null) {
                        lol = new LinkedList<>();
                    }
                    lol.addLast(ll.pollFirst());
                    reduced.put(e.getKey(), lol);
                }
            }
        }
        for(Map.Entry<Integer, LinkedList<Tuple>> e : reduced.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            while(ll.size()>1) {
                pl(ll.pollFirst().idx + " " + ll.pollFirst().idx);
            }
        }

        ArrayList<Tuple> final_left = new ArrayList<>();
        for(Map.Entry<Integer, LinkedList<Tuple>> e : reduced.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            while(ll.size()>0) {
                final_left.add(ll.pollFirst());
            }
        }

        for(int i=0;i<final_left.size();i+=2) {
            pl(final_left.get(i).idx+" "+final_left.get(i+1).idx);
        }
        pw.flush();
        pw.close();
    }
    /*
10
1 2 3
1 2 4
1 2 5
1 3 3
1 3 4
1 3 5
1 4 3
1 4 4
1 4 5
2 1 3
     */
    static class Tuple implements Comparable<Tuple> {
        int x,idx;
        Tuple(int x, int idx) {
            this.x = x;
            this.idx = idx;
        }
        public int compareTo(Tuple o) {
            return this.x - o.x;
        }
        public String toString() {
            return "{" + x + " ," + idx+ " }";
        }
    }
    static int ni() throws IOException
    {
        return scan.nextInt();
    }
    static long nl() throws IOException
    {
        return scan.nextLong();
    }
    static double nd() throws IOException
    {
        return scan.nextDouble();
    }
    static void pl()
    {
        pw.println();
    }
    static void p(Object o)
    {
        pw.print(o+" ");
    }
    static void pl(Object o)
    {
        pw.println(o);
    }
    static void psb(StringBuilder sb)
    {
        pw.print(sb);
    }
    static void pa(String arrayName, Object arr[])
    {
        pl(arrayName+" : ");
        for(Object o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, int arr[])
    {
        pl(arrayName+" : ");
        for(int o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, long arr[])
    {
        pl(arrayName+" : ");
        for(long o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, double arr[])
    {
        pl(arrayName+" : ");
        for(double o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, char arr[])
    {
        pl(arrayName+" : ");
        for(char o : arr)
            p(o);
        pl();
    }
    static void pa(String listName, List list)
    {
        pl(listName+" : ");
        for(Object o : list)
            p(o);
        pl();
    }
    static void pa(String arrayName, Object[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(Object o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, int[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(int o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, long[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(long o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, char[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(char o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, double[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(double o : arr[i])
                p(o);
            pl();
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
}


/**
 * BaZ :D
 */

import java.lang.reflect.Array;
import java.util.*;
import java.io.*;
import static java.lang.Math.*;

public class ACMIND
{
    static FastReader scan;
    static PrintWriter pw;
    static long MOD = 1_000_000_007;
    static long INF = 1_000_000_000_000_000_000L;
    static long inf = 2_000_000_000;
    public static void main(String[] args) {
        new Thread(null,null,"BaZ",1<<27)
        {
            public void run()
            {
                try
                {
                    solve();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }.start();
    }

    static void solve() throws IOException
    {
        scan = new FastReader();
        pw = new PrintWriter(System.out,true);
        StringBuilder sb = new StringBuilder();
        int n = ni();
        TreeMap<Integer, TreeMap<Integer, LinkedList<Tuple>>> map = new TreeMap<>();
        for(int i=0;i<n;++i) {
            int x = ni(), y  = ni(), z = ni();
            TreeMap<Integer, LinkedList<Tuple>> ll = map.get(x);
            if(ll==null) {
                ll = new TreeMap<>();
            }
            LinkedList<Tuple> lll = ll.get(y);
            if(lll == null) {
                lll = new LinkedList<>();
            }
            lll.addLast(new Tuple(z, i+1));
            ll.put(y, lll);
            map.put(x, ll);
        }
        for(Map.Entry<Integer, TreeMap<Integer, LinkedList<Tuple>>> e : map.entrySet()) {
            Map<Integer, LinkedList<Tuple>> mapy = e.getValue();
            for(Map.Entry<Integer, LinkedList<Tuple>> f : mapy.entrySet()) {
                LinkedList<Tuple> ll = f.getValue();
                Collections.sort(ll);
                //pa("ll", ll);
                while(ll.size()>1) {
                    pl(ll.pollFirst().idx + " " + ll.pollFirst().idx);
                }
            }
        }
        TreeMap<Integer, LinkedList<Tuple>> reduced = new TreeMap<>();
        for(Map.Entry<Integer, TreeMap<Integer, LinkedList<Tuple>>> e : map.entrySet()) {
            Map<Integer, LinkedList<Tuple>> mapy = e.getValue();
            for(Map.Entry<Integer, LinkedList<Tuple>> f : mapy.entrySet()) {
                LinkedList<Tuple> ll = f.getValue();
                if(ll.size()>0) {
                    LinkedList<Tuple> lol = reduced.get(e.getKey());
                    if(lol==null) {
                        lol = new LinkedList<>();
                    }
                    lol.addLast(ll.pollFirst());
                    reduced.put(e.getKey(), lol);
                }
            }
        }
        for(Map.Entry<Integer, LinkedList<Tuple>> e : reduced.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            Collections.sort(ll);
            while(ll.size()>1) {
                pl(ll.pollFirst().idx + " " + ll.pollFirst().idx);
            }
        }

        ArrayList<Tuple> final_left = new ArrayList<>();
        for(Map.Entry<Integer, LinkedList<Tuple>> e : reduced.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            Collections.sort(ll);
            while(ll.size()>0) {
                final_left.add(ll.pollFirst());
            }
        }

        for(int i=0;i<final_left.size();i+=2) {
            pl(final_left.get(i).idx+" "+final_left.get(i+1).idx);
        }
        pw.flush();
        pw.close();
    }
    static class Tuple implements Comparable<Tuple> {
        int x,idx;
        Tuple(int x, int idx) {
            this.x = x;
            this.idx = idx;
        }
        public int compareTo(Tuple o) {
            return this.x - o.x;
        }
        public String toString() {
            return "{" + x + " ," + idx+ " }";
        }
    }
    static int ni() throws IOException
    {
        return scan.nextInt();
    }
    static long nl() throws IOException
    {
        return scan.nextLong();
    }
    static double nd() throws IOException
    {
        return scan.nextDouble();
    }
    static void pl()
    {
        pw.println();
    }
    static void p(Object o)
    {
        pw.print(o+" ");
    }
    static void pl(Object o)
    {
        pw.println(o);
    }
    static void psb(StringBuilder sb)
    {
        pw.print(sb);
    }
    static void pa(String arrayName, Object arr[])
    {
        pl(arrayName+" : ");
        for(Object o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, int arr[])
    {
        pl(arrayName+" : ");
        for(int o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, long arr[])
    {
        pl(arrayName+" : ");
        for(long o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, double arr[])
    {
        pl(arrayName+" : ");
        for(double o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, char arr[])
    {
        pl(arrayName+" : ");
        for(char o : arr)
            p(o);
        pl();
    }
    static void pa(String listName, List list)
    {
        pl(listName+" : ");
        for(Object o : list)
            p(o);
        pl();
    }
    static void pa(String arrayName, Object[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(Object o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, int[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(int o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, long[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(long o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, char[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(char o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, double[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(double o : arr[i])
                p(o);
            pl();
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
}


/**
 * BaZ :D
 */

import java.lang.reflect.Array;
import java.util.*;
import java.io.*;
import static java.lang.Math.*;

public class ACMIND
{
    static FastReader scan;
    static PrintWriter pw;
    static long MOD = 1_000_000_007;
    static long INF = 1_000_000_000_000_000_000L;
    static long inf = 2_000_000_000;
    public static void main(String[] args) {
        new Thread(null,null,"BaZ",1<<27)
        {
            public void run()
            {
                try
                {
                    solve();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }.start();
    }

    static void solve() throws IOException
    {
        scan = new FastReader();
        pw = new PrintWriter(System.out,true);
        StringBuilder sb = new StringBuilder();
        int n = ni();
        Map<Integer, LinkedList<Tuple>> map = new HashMap<>();
        for(int i=0;i<n;++i) {
            int x = ni(), y  = ni(), z = ni();
            LinkedList<Tuple> ll = map.get(x);
            if(ll==null) {
                ll = new LinkedList<>();
            }
            ll.addFirst(new Tuple(y,z,i+1));
            map.put(x, ll);
        }
        for(int e : map.keySet()) {
            Collections.sort(map.get(e));
        }
        for(Map.Entry<Integer, LinkedList<Tuple>> e : map.entrySet()) {
            LinkedList<Tuple> ll = e.getValue();
            while(ll.size()>1) {
                pl(ll.pollFirst().idx+ " " + ll.pollFirst().idx);
            }
        }
        ArrayList<Integer> rem = new ArrayList<>();
        for(Map.Entry<Integer, LinkedList<Tuple>> e : map.entrySet()) {
            if(e.getValue().size()>0) {
                rem.add(e.getKey());
            }
        }
        //pa("rem", rem);
        Collections.sort(rem);
        for(int i=0;i<rem.size();i+=2) {
            pl(map.get(rem.get(i)).pollFirst().idx + " "+ map.get(rem.get(i+1)).pollFirst().idx);
        }
        pw.flush();
        pw.close();
    }
    static class Tuple implements Comparable<Tuple> {
        int y,z,idx;
        Tuple(int y, int z, int idx) {
            this.y = y;
            this.z = z;
            this.idx = idx;
        }
        public int compareTo(Tuple o) {
            if(this.y!=o.y) {
                return this.y - o.y;
            }
            return this.z - o.z;
        }
    }
    static int ni() throws IOException
    {
        return scan.nextInt();
    }
    static long nl() throws IOException
    {
        return scan.nextLong();
    }
    static double nd() throws IOException
    {
        return scan.nextDouble();
    }
    static void pl()
    {
        pw.println();
    }
    static void p(Object o)
    {
        pw.print(o+" ");
    }
    static void pl(Object o)
    {
        pw.println(o);
    }
    static void psb(StringBuilder sb)
    {
        pw.print(sb);
    }
    static void pa(String arrayName, Object arr[])
    {
        pl(arrayName+" : ");
        for(Object o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, int arr[])
    {
        pl(arrayName+" : ");
        for(int o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, long arr[])
    {
        pl(arrayName+" : ");
        for(long o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, double arr[])
    {
        pl(arrayName+" : ");
        for(double o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, char arr[])
    {
        pl(arrayName+" : ");
        for(char o : arr)
            p(o);
        pl();
    }
    static void pa(String listName, List list)
    {
        pl(listName+" : ");
        for(Object o : list)
            p(o);
        pl();
    }
    static void pa(String arrayName, Object[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(Object o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, int[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(int o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, long[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(long o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, char[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(char o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, double[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(double o : arr[i])
                p(o);
            pl();
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
}


/**
 * BaZ :D
 */

import java.lang.reflect.Array;
import java.util.*;
import java.io.*;
import static java.lang.Math.*;

public class ACMIND
{
    static FastReader scan;
    static PrintWriter pw;
    static long MOD = 1_000_000_007;
    static long INF = 1_000_000_000_000_000_000L;
    static long inf = 2_000_000_000;
    public static void main(String[] args) {
        new Thread(null,null,"BaZ",1<<27)
        {
            public void run()
            {
                try
                {
                    solve();
                }
                catch(Exception e)
                {
                    e.printStackTrace();
                    System.exit(1);
                }
            }
        }.start();
    }

    static void solve() throws IOException
    {
        scan = new FastReader();
        pw = new PrintWriter(System.out,true);
        StringBuilder sb = new StringBuilder();
        int n = ni();
        Tuple points[] = new Tuple[n];
        for(int i=0;i<n;++i) {
            points[i] = new Tuple(ni(), ni(), ni(), i);
        }
        Arrays.sort(points);
        for(int i=0;i<n;i+=2) {
            pl((points[i].idx+1)+" "+(points[i+1].idx+1));
        }
        pw.flush();
        pw.close();
    }
    static class Tuple implements Comparable<Tuple> {
        int x,y,z,idx;
        Tuple(int x, int y, int z, int idx) {
            this.x = x;
            this.y = y;
            this.z = z;
            this.idx = idx;
        }
        public int compareTo(Tuple o) {
            if(this.x!=o.x) {
                return this.x - o.x;
            }
            if(this.y!=o.y) {
                return this.y - o.y;
            }
            return this.z - o.z;
        }
    }
    static int ni() throws IOException
    {
        return scan.nextInt();
    }
    static long nl() throws IOException
    {
        return scan.nextLong();
    }
    static double nd() throws IOException
    {
        return scan.nextDouble();
    }
    static void pl()
    {
        pw.println();
    }
    static void p(Object o)
    {
        pw.print(o+" ");
    }
    static void pl(Object o)
    {
        pw.println(o);
    }
    static void psb(StringBuilder sb)
    {
        pw.print(sb);
    }
    static void pa(String arrayName, Object arr[])
    {
        pl(arrayName+" : ");
        for(Object o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, int arr[])
    {
        pl(arrayName+" : ");
        for(int o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, long arr[])
    {
        pl(arrayName+" : ");
        for(long o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, double arr[])
    {
        pl(arrayName+" : ");
        for(double o : arr)
            p(o);
        pl();
    }
    static void pa(String arrayName, char arr[])
    {
        pl(arrayName+" : ");
        for(char o : arr)
            p(o);
        pl();
    }
    static void pa(String listName, List list)
    {
        pl(listName+" : ");
        for(Object o : list)
            p(o);
        pl();
    }
    static void pa(String arrayName, Object[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(Object o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, int[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(int o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, long[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(long o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, char[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(char o : arr[i])
                p(o);
            pl();
        }
    }
    static void pa(String arrayName, double[][] arr) {
        pl(arrayName+" : ");
        for(int i=0;i<arr.length;++i) {
            for(double o : arr[i])
                p(o);
            pl();
        }
    }
    static class FastReader {
        final private int BUFFER_SIZE = 1 << 16;
        private DataInputStream din;
        private byte[] buffer;
        private int bufferPointer, bytesRead;

        public FastReader() {
            din = new DataInputStream(System.in);
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public FastReader(String file_name) throws IOException {
            din = new DataInputStream(new FileInputStream(file_name));
            buffer = new byte[BUFFER_SIZE];
            bufferPointer = bytesRead = 0;
        }

        public String readLine() throws IOException {
            byte[] buf = new byte[1000000];
            int cnt = 0, c;
            while ((c = read()) != -1) {
                if (c == '\n') break;
                buf[cnt++] = (byte) c;
            }
            return new String(buf, 0, cnt);
        }

        public int nextInt() throws IOException {
            int ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public long nextLong() throws IOException {
            long ret = 0;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (neg) return -ret;
            return ret;
        }

        public double nextDouble() throws IOException {
            double ret = 0, div = 1;
            byte c = read();
            while (c <= ' ') c = read();
            boolean neg = (c == '-');
            if (neg) c = read();
            do {
                ret = ret * 10 + c - '0';
            } while ((c = read()) >= '0' && c <= '9');
            if (c == '.') while ((c = read()) >= '0' && c <= '9') ret += (c - '0') / (div *= 10);
            if (neg) return -ret;
            return ret;
        }

        private void fillBuffer() throws IOException {
            bytesRead = din.read(buffer, bufferPointer = 0, BUFFER_SIZE);
            if (bytesRead == -1) buffer[0] = -1;
        }

        private byte read() throws IOException {
            if (bufferPointer == bytesRead) fillBuffer();
            return buffer[bufferPointer++];
        }

        public void close() throws IOException {
            if (din == null) return;
            din.close();
        }
    }
}


import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;
import static java.lang.Math.abs;
import static java.lang.System.exit;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.StringTokenizer;

public class C {

	interface IntComparator {
		public int compare(int o1, int o2);
	}

	static void sort(int a[], int n, IntComparator cmp) {
		if (n == 0) {
			return;
		}
		for (int i = 1; i < n; i++) {
			int j = i;
			int ca = a[i];
			do {
				int nj = (j - 1) >> 1;
				int na = a[nj];
				if (cmp.compare(na, ca) >= 0) {
					break;
				}
				a[j] = na;
				j = nj;
			} while (j != 0);
			a[j] = ca;
		}
		int ca = a[0];
		for (int i = n - 1; i > 0; i--) {
			int j = 0;
			while ((j << 1) + 2 + Integer.MIN_VALUE < i + Integer.MIN_VALUE) {
				j <<= 1;
				j += (cmp.compare(a[j + 1], a[j + 2]) < 0) ? 2 : 1;
			}
			if ((j << 1) + 2 == i) {
				j = (j << 1) + 1;
			}
			int na = a[i];
			a[i] = ca;
			ca = na;
			while (j != 0 && cmp.compare(a[j], ca) < 0) {
				j = (j - 1) >> 1;
			}
			while (j != 0) {
				na = a[j];
				a[j] = ca;
				ca = na;
				j = (j - 1) >> 1;
			}
		}
		a[0] = ca;
	}

	static void solve() throws Exception {
		int n = scanInt();
		final int x[] = new int[n], y[] = new int[n], z[] = new int[n];
		int idx[] = new int[n];
		for (int i = 0; i < n; i++) {
			x[i] = scanInt();
			y[i] = scanInt();
			z[i] = scanInt();
			idx[i] = i;
		}
		sort(idx, n, new IntComparator() {
			public int compare(int o1, int o2) {
				return x[o1] != x[o2] ? x[o1] - x[o2] : y[o1] != y[o2] ? y[o1] - y[o2] : z[o1] - z[o2];
			}
		});
		boolean taken[] = new boolean[n];
		for (int ii = 0, i, jj, j; ii < n; ii++) {
			if (taken[i = idx[ii]]) {
				continue;
			}
			taken[i] = true;
			for (jj = ii + 1;; jj++) {
				if (!taken[j = idx[jj]]) {
					break;
				}
			}
			if (x[i] != x[j]) {
				int yi = y[i], zi = z[i], xj = x[j], zd = abs(y[j] - yi) + abs(z[j] - zi);
				for (++jj; jj < n; jj++) {
					int j2 = idx[jj], zt;
					if (x[j2] != xj) {
						break;
					}
					if (!taken[j2] && (zt = abs(y[j2] - yi) + abs(z[j2] - zi)) < zd) {
						j = j2;
						zd = zt;
					}
				}
			} else if (y[i] != y[j]) {
				int zi = z[i], zd = abs(z[j] - z[i]), xj = x[j], yj = y[j];
				for (++jj; jj < n; jj++) {
					int j2 = idx[jj], zt;
					if (x[j2] != xj || y[j2] != yj) {
						break;
					}
					if (!taken[j2] && (zt = abs(z[j2] - zi)) < zd) {
						j = j2;
						zd = zt;
					}
				}
			}
			taken[j] = true;
			out.println((i + 1) + " " + (j + 1));
		}
	}

	static int scanInt() throws IOException {
		return parseInt(scanString());
	}

	static long scanLong() throws IOException {
		return parseLong(scanString());
	}

	static String scanString() throws IOException {
		while (tok == null || !tok.hasMoreTokens()) {
			tok = new StringTokenizer(in.readLine());
		}
		return tok.nextToken();
	}

	static BufferedReader in;
	static PrintWriter out;
	static StringTokenizer tok;

	public static void main(String[] args) {
		try {
			in = new BufferedReader(new InputStreamReader(System.in));
			out = new PrintWriter(System.out);
			solve();
			in.close();
			out.close();
		} catch (Throwable e) {
			e.printStackTrace();
			exit(1);
		}
	}
}


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

public class codeforces {
	public static PrintWriter out;
	public static class MyScanner {
		BufferedReader br;
		StringTokenizer st;
		public MyScanner() {
			br = new BufferedReader(new InputStreamReader(System.in));
		}
		String next() {
			while (st == null || !st.hasMoreElements()) {
				try {
					st = new StringTokenizer(br.readLine());
				}
				catch (IOException e) {
					e.printStackTrace();
				}
			}
			return st.nextToken();
		}
		int nextInt() {
			return Integer.parseInt(next());
		}
		long nextLong() {
			return Long.parseLong(next());
		}
		double nextDouble() {
			return Double.parseDouble(next());
		}
		String nextLine() {
			String str = "";
			try {
				str = br.readLine();
			}
			catch (IOException e) {
				e.printStackTrace();
			}
			return str;
		}
	}
	// The next two void functions are shuffle functions on an array.
	// This way, quick sort encounters the worst case scenario with low probability.
	// A reason for doing this is if we don't a primitive data array to an object array.
	// For safety reasons, it would be preferable to use object arrays unless doing so expends a lot of time.
	public static void shuffleLongArray(long[] arr) {
		int n = arr.length;
		Random rnd = new Random();
        for (int i=0; i < n; i++) {
        	long tmp = arr[i];
        	int randomPos = i + rnd.nextInt(n-i);
        	arr[i] = arr[randomPos];
        	arr[randomPos] = tmp;
        }
	}
	public static void shuffleIntArray(int[] arr) {
		int n = arr.length;
		Random rnd = new Random();
        for (int i=0; i < n; i++) {
        	int tmp = arr[i];
        	int randomPos = i + rnd.nextInt(n-i);
        	arr[i] = arr[randomPos];
        	arr[randomPos] = tmp;
        }
	}
	public static void main (String[] args) throws IOException {
    	MyScanner sc = new MyScanner();
    	out = new PrintWriter(new BufferedOutputStream(System.out));
    	// Write your solution here.
    	int n = sc.nextInt();
    	// n is always even
    	double[][] points = new double[n][4];
    	double[][] blast = new double[n][2];
    	double y = Math.pow(2.0, 1.0/3.0);
    	for (int i=0; i < n; i++) {
    		points[i][0] = sc.nextDouble(); points[i][1] = sc.nextDouble(); points[i][2] = sc.nextDouble();
    		double j = (double) i;
    		points[i][3] = j+1.0;
    	}
    	// Shuffle double array
		Random rnd = new Random();
        for (int i=0; i < n; i++) {
        	int randomPos = i + rnd.nextInt(n-i);
        	for (int j=0; j < 4; j++) {
        		double tmp = points[i][j];
        		points[i][j] = points[randomPos][j];
        		points[randomPos][j] = tmp;
        	}
        }
		Arrays.sort(points, new Comparator<double[]>() {
			public int compare (double[] a, double[] b) {
				if (Double.compare(a[0], b[0]) != 0) return Double.compare(a[0], b[0]);
				if (Double.compare(a[1], b[1]) != 0) return Double.compare(a[1], b[1]);
				return Double.compare(a[2], b[2]);
			}
		});
		/*
		for (int i=0; i < n; i++) {
			for (int j=0; j < 4; j++) {
				out.print(points[i][j] + " ");
			}
			out.println();
		}
		*/
		ArrayList<Integer> fiber = new ArrayList<Integer>();
		for (int i=0; i < n; i++) {
			if (i < n-1 && points[i][0] == points[i+1][0] && points[i][1] == points[i+1][1]) {
				int a = (int) points[i][3]; int b = (int) points[i+1][3];
				out.print(a + " " + b);
				out.println();
				i++;
				continue;
			}
			else {
				fiber.add(i);
			}
		}
		double[][] shrink = new double[fiber.size()][4];
		for (int i=0; i < fiber.size(); i++) {
			for (int j=0; j < 4; j++) {
				shrink[i][j] = points[fiber.get(i)][j];
			}
		}
		// shrink is already sorted
		ArrayList<Integer> cell = new ArrayList<Integer>();
		for (int i=0; i < shrink.length; i++) {
			if (i < shrink.length-1 && shrink[i][0] == shrink[i+1][0]) {
				int a = (int) shrink[i][3]; int b = (int) shrink[i+1][3];
				out.print(a + " " + b);
				out.println();
				i++;
				continue;
			}
			else {
				cell.add(i);
			}
		}
		double[][] thanks = new double[cell.size()][4];
		for (int i=0; i < cell.size(); i++) {
			for (int j=0; j < 4; j++) {
				thanks[i][j] = shrink[cell.get(i)][j];
			}
		}
		for (int i=0; i < thanks.length; i++) {
			if (i < thanks.length-1) {
				int a = (int) thanks[i][3]; int b = (int) thanks[i+1][3];
				out.print(a + " " + b);
				out.println();
				i++;
				continue;
			}
			else {
				cell.add(i);
			}
		}
    	out.close();
	}

}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.io.IOException;
import java.util.stream.Collectors;
import java.util.TreeSet;
import java.util.ArrayList;
import java.io.UncheckedIOException;
import java.util.List;
import java.util.stream.Stream;
import java.util.Map;
import java.io.Closeable;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.util.Comparator;
import java.io.InputStream;

/**
 * Built using CHelper plug-in
 * Actual solution is at the top
 *
 * @author daltao
 */
public class Main {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread(null, new TaskAdapter(), "daltao", 1 << 27);
        thread.start();
        thread.join();
    }

    static class TaskAdapter implements Runnable {
        @Override
        public void run() {
            InputStream inputStream = System.in;
            OutputStream outputStream = System.out;
            FastInput in = new FastInput(inputStream);
            FastOutput out = new FastOutput(outputStream);
            TaskC2 solver = new TaskC2();
            solver.solve(1, in, out);
            out.close();
        }
    }

    static class TaskC2 {
        public void solve(int testNumber, FastInput in, FastOutput out) {
            int n = in.readInt();

            List<Point> ptList = new ArrayList<>(n);
            for (int i = 0; i < n; i++) {
                Point pt = new Point();
                pt.x = in.readInt();
                pt.y = in.readInt();
                pt.z = in.readInt();
                pt.id = i + 1;

                ptList.add(pt);
            }

            Map<Integer, List<Point>> groupByX = ptList.stream()
                    .collect(Collectors.groupingBy((x) -> x.x));

            TreeSet<Point> wait = new TreeSet<>(Point.sortByXYZ);
            for (List<Point> sameX : groupByX.values()) {
                TreeSet<Point> xQ = new TreeSet<>(Point.sortByXYZ);
                Map<Integer, List<Point>> groupByY = sameX.stream()
                        .collect(Collectors.groupingBy((x) -> x.y));
                for (List<Point> sameY : groupByY.values()) {
                    TreeSet<Point> yQ = new TreeSet<>(Point.sortByXYZ);
                    Map<Integer, List<Point>> groupByZ = sameY.stream()
                            .collect(Collectors.groupingBy((x) -> x.z));
                    for (List<Point> sameZ : groupByZ.values()) {
                        int m = sameZ.size();
                        int i;
                        for (i = 0; i + 1 < m; i += 2) {
                            answer(sameZ.get(i), sameZ.get(i + 1), out);
                        }
                        if (i < m) {
                            yQ.add(sameZ.get(i));
                        }
                    }

                    while (yQ.size() >= 2) {
                        answer(yQ.pollFirst(), yQ.pollFirst(), out);
                    }

                    xQ.addAll(yQ);
                }

                while (xQ.size() >= 2) {
                    answer(xQ.pollFirst(), xQ.pollFirst(), out);
                }
                wait.addAll(xQ);
            }

            while (wait.size() >= 2) {
                answer(wait.pollFirst(), wait.pollFirst(), out);
            }
        }

        public void answer(Point a, Point b, FastOutput out) {
            out.append(a.id).append(' ').append(b.id).append('\n');
        }

    }

    static class FastInput {
        private final InputStream is;
        private byte[] buf = new byte[1 << 13];
        private int bufLen;
        private int bufOffset;
        private int next;

        public FastInput(InputStream is) {
            this.is = is;
        }

        private int read() {
            while (bufLen == bufOffset) {
                bufOffset = 0;
                try {
                    bufLen = is.read(buf);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
                if (bufLen == -1) {
                    return -1;
                }
            }
            return buf[bufOffset++];
        }

        public void skipBlank() {
            while (next >= 0 && next <= 32) {
                next = read();
            }
        }

        public int readInt() {
            int sign = 1;

            skipBlank();
            if (next == '+' || next == '-') {
                sign = next == '+' ? 1 : -1;
                next = read();
            }

            int val = 0;
            if (sign == 1) {
                while (next >= '0' && next <= '9') {
                    val = val * 10 + next - '0';
                    next = read();
                }
            } else {
                while (next >= '0' && next <= '9') {
                    val = val * 10 - next + '0';
                    next = read();
                }
            }

            return val;
        }

    }

    static class Point {
        int id;
        int x;
        int y;
        int z;
        static Comparator<Point> sortByXYZ = (a, b) -> a.x != b.x ? a.x - b.x : a.y != b.y ? a.y - b.y : a.z - b.z;

        public int hashCode() {
            int h = x;
            h = h * 31 + y;
            h = h * 31 + z;
            return h;
        }

        public boolean equals(Object obj) {
            Point pt = (Point) obj;
            return x == pt.x && y == pt.y && z == pt.z;
        }

    }

    static class FastOutput implements AutoCloseable, Closeable {
        private StringBuilder cache = new StringBuilder(1 << 20);
        private final Writer os;

        public FastOutput(Writer os) {
            this.os = os;
        }

        public FastOutput(OutputStream os) {
            this(new OutputStreamWriter(os));
        }

        public FastOutput append(char c) {
            cache.append(c);
            return this;
        }

        public FastOutput append(int c) {
            cache.append(c);
            return this;
        }

        public FastOutput flush() {
            try {
                os.append(cache);
                cache.setLength(0);
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
            return this;
        }

        public void close() {
            flush();
            try {
                os.close();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }

    }
}


// @uthor -: puneet
// TimeStamp -: 9:30 PM - 16/10/19

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

public class Q33 implements Runnable {

    public void solve() {
        int n=in.ni();
        ArrayList<Point> list=new ArrayList<>();
        for(int i=0;i<n;++i){
            list.add(new Point(in.ni(), in.ni(), in.ni(), i + 1));
        }

        try {
            list.sort((p1, p2) -> {
                if (p1.x == p2.x) {
                    if (p1.y == p2.y)
                        return p1.z - p2.z;
                    return p1.y - p2.y;
                }
                return p1.x - p2.x;
            });
        }catch (Exception e){
            out.println("hhw");
        }
        ArrayList<Point> list2=new ArrayList<>();

        for(int i=0;i<n;++i){
            if( i+1<n && list.get(i).x==list.get(i+1).x && list.get(i).y==list.get(i+1).y){
                out.println(list.get(i).idx+" "+list.get(i+1).idx);
                i++;
//                list.get(i).isset=true;
//                list.get(i+1).isset=true;
            }else {
                list2.add(list.get(i));
            }
        }

//        if(!list.get(list.size()-1).isset){
//            list2.add(list.get(list.size()-1));
//        }

        ArrayList<Point> list3=new ArrayList<>();

        for(int i=0;i<list2.size();++i){
            if(i+1<list2.size() && list2.get(i).x==list2.get(i+1).x){
                out.println(list2.get(i).idx+" "+list2.get(i+1).idx);
                i++;
            }else {
                list3.add(list2.get(i));
            }
        }

//        if(!list2.get(list2.size()-1).isset){
//            list3.add(list2.get(list2.size()-1));
//        }

        for(int i=0;i<list3.size()-1;i+=2){
            out.println(list3.get(i).idx+" "+list3.get(i+1).idx);
        }
    }

    static class Point{
        int x,y,z,idx;
        boolean isset=false;

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

    /* -------------------- Templates and Input Classes -------------------------------*/

    @Override
    public void run() {
        try {
            init();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        solve();
        out.flush();
    }

    private FastInput in;
    private PrintWriter out;

    public static void main(String[] args) throws Exception {
        new Thread(null, new Q33(), "Main", 1 << 27).start();
    }

    private void init() throws FileNotFoundException {
        InputStream inputStream = System.in;
        OutputStream outputStream = System.out;
        try {
            if (System.getProperty("user.name").equals("puneet")) {
                outputStream = new FileOutputStream("/home/puneet/Desktop/output.txt");
                inputStream = new FileInputStream("/home/puneet/Desktop/input.txt");
            }
        } catch (Exception ignored) {
        }
        out = new PrintWriter(outputStream);
        in = new FastInput(inputStream);
    }

    private void sort(int[] arr) {
        List<Integer> list = new ArrayList<>();
        for (int object : arr) list.add(object);
        Collections.sort(list);
        for (int i = 0; i < list.size(); ++i) arr[i] = list.get(i);
    }

    private void sort(long[] arr) {
        List<Long> list = new ArrayList<>();
        for (long object : arr) list.add(object);
        Collections.sort(list);
        for (int i = 0; i < list.size(); ++i) arr[i] = list.get(i);
    }

    public long ModPow(long x, long y, long MOD) {
        long res = 1L;
        x = x % MOD;
        while (y >= 1) {
            if ((y & 1) > 0) res = (res * x) % MOD;
            x = (x * x) % MOD;
            y >>= 1;
        }
        return res;
    }

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

    public long gcd(long a, long b) {
        if (a == 0) return b;
        return gcd(b % a, a);
    }

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

        public FastInput(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 peek() {
            if (numChars == -1) {
                return -1;
            }
            if (curChar >= numChars) {
                curChar = 0;
                try {
                    numChars = stream.read(buf);
                } catch (IOException e) {
                    return -1;
                }
                if (numChars <= 0) {
                    return -1;
                }
            }
            return buf[curChar];
        }

        public int ni() {
            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 long nl() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            long 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 String ns() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            StringBuilder res = new StringBuilder();
            do {
                if (Character.isValidCodePoint(c)) {
                    res.appendCodePoint(c);
                }
                c = read();
            } while (!isSpaceChar(c));
            return res.toString();
        }

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

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

        private String readLine0() {
            StringBuilder buf = new StringBuilder();
            int c = read();
            while (c != '\n' && c != -1) {
                if (c != '\r') {
                    buf.appendCodePoint(c);
                }
                c = read();
            }
            return buf.toString();
        }

        public String readLine() {
            String s = readLine0();
            while (s.trim().length() == 0) {
                s = readLine0();
            }
            return s;
        }

        public String readLine(boolean ignoreEmptyLines) {
            if (ignoreEmptyLines) {
                return readLine();
            } else {
                return readLine0();
            }
        }

        public BigInteger readBigInteger() {
            try {
                return new BigInteger(ns());
            } catch (NumberFormatException e) {
                throw new InputMismatchException();
            }
        }

        public char nc() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            return (char) c;
        }

        public double nd() {
            int c = read();
            while (isSpaceChar(c)) {
                c = read();
            }
            int sgn = 1;
            if (c == '-') {
                sgn = -1;
                c = read();
            }
            double res = 0;
            while (!isSpaceChar(c) && c != '.') {
                if (c == 'e' || c == 'E') {
                    return res * Math.pow(10, ni());
                }
                if (c < '0' || c > '9') {
                    throw new InputMismatchException();
                }
                res *= 10;
                res += c - '0';
                c = read();
            }
            if (c == '.') {
                c = read();
                double m = 1;
                while (!isSpaceChar(c)) {
                    if (c == 'e' || c == 'E') {
                        return res * Math.pow(10, ni());
                    }
                    if (c < '0' || c > '9') {
                        throw new InputMismatchException();
                    }
                    m /= 10;
                    res += (c - '0') * m;
                    c = read();
                }
            }
            return res * sgn;
        }

        public String next() {
            return ns();
        }

        public SpaceCharFilter getFilter() {
            return filter;
        }

        public void setFilter(SpaceCharFilter filter) {
            this.filter = filter;
        }

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

        public int[] intarr(int n) {
            int arr[] = new int[n];
            for (int i = 0; i < n; i++) {
                arr[i] = ni();
            }
            return arr;
        }

        public double[] doublearr(int n) {
            double arr[] = new double[n];
            for (int i = 0; i < n; i++) {
                arr[i] = nd();
            }
            return arr;
        }

        public double[][] doublearr(int n, int m) {
            double[][] arr = new double[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    arr[i][j] = nd();
                }
            }
            return arr;
        }

        public int[][] intarr(int n, int m) {
            int[][] arr = new int[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    arr[i][j] = ni();
                }
            }
            return arr;
        }

        public long[][] longarr(int n, int m) {
            long[][] arr = new long[n][m];
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < m; j++) {
                    arr[i][j] = nl();
                }
            }
            return arr;
        }

        public long[] longarr(int n) {
            long arr[] = new long[n];
            for (int i = 0; i < n; i++) {
                arr[i] = nl();
            }
            return arr;
        }

    }

}


import java.util.*;

import java.io.*;

public class C {
	static ArrayList<Integer> primes;
	static int[] isComposite;
	static int[] idx;
	static int[] p;

	static void sieve(int N) // O(N log log N)
	{
		isComposite = new int[N + 1];
		idx = new int[N + 1];
		isComposite[0] = isComposite[1] = 1; // 0 indicates a prime number
		primes = new ArrayList<Integer>();

		int ix = 1;
		for (int i = 2; i <= N; ++i) // can loop till i*i <= N if primes array is not needed O(N log log sqrt(N))
			if (isComposite[i] == 0) // can loop in 2 and odd integers for slightly better performance
			{
				primes.add(i);
				idx[i] = ix;
				ix++;
				if (1l * i * i <= N)
					for (int j = i * i; j <= N; j += i) // j = i * 2 will not affect performance too much, may alter in
														// modified sieve
						isComposite[j] = 1;
			}
		p = new int[primes.size()];
		int i = 0;
		for (int x : primes) {
			p[i++] = x;
		}
	}

	public static void main(String[] args) throws NumberFormatException, IOException {
		// TODO Auto-generated method stub
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		PrintWriter pw = new PrintWriter(System.out);
		int t = Integer.parseInt(br.readLine());
		pair[] p = new pair[t];
		for (int i = 0; i < p.length; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine());
			int a = Integer.parseInt(st.nextToken());
			int b = Integer.parseInt(st.nextToken());
			int c = Integer.parseInt(st.nextToken());
			p[i] = new pair(a, b, c, i + 1);
		}
		Arrays.sort(p);
		ArrayList<pair1> rz = new ArrayList<>();
		ArrayList<pair2> ans = new ArrayList<pair2>();
		ans.add(new pair2(p[0].x, p[0].y, p[0].idx));
		int z = p[0].z;
		for (int i = 1; i <= t; i++) {
			while (i < p.length && p[i].z == z) {
				ans.add(new pair2(p[i].x, p[i].y, p[i].idx));
				i++;
			}
			Collections.sort(ans);
			ArrayList<pair1> ry = new ArrayList<>();
			ArrayList<pair1> a = new ArrayList<>();
			a.add(new pair1(ans.get(0).x, ans.get(0).idx));
			int y = ans.get(0).y;
			for (int j = 1; j <= ans.size(); j++) {
				while (j < ans.size() && ans.get(j).y == y) {
					a.add(new pair1(ans.get(j).x, ans.get(j).idx));
					j++;
				}
				Collections.sort(a);
				if (a.size() % 2 == 1)
					ry.add(a.get(a.size() - 1));
				for (int w = 0; w < a.size() - 1; w += 2) {
					pw.println(a.get(w).idx + " " + a.get(w + 1).idx);
				}
				if (j != ans.size()) {
					a = new ArrayList<>();
					a.add(new pair1(ans.get(j).x, ans.get(j).idx));
					y = ans.get(j).y;
				}
			}
			if (ry.size() % 2 == 1)
				rz.add(ry.get(ry.size() - 1));
			for (int w = 0; w < ry.size() - 1; w += 2) {
				pw.println(ry.get(w).idx + " " + ry.get(w + 1).idx);
			}
			if(i!=p.length) {
				z=p[i].z;
				ans=new ArrayList<>();
				ans.add(new pair2(p[i].x, p[i].y, p[i].idx));
			}
		}
		for (int i = 0; i < rz.size(); i += 2) {
			pw.println(rz.get(i).idx + " " + rz.get(i + 1).idx);
		}
		pw.flush();

	}

	static int g_div(int x) {
		for (int i = 2; i * i <= x; i++)
			if (x % i == 0)
				return x / i;
		return 1;
	}

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

	static class pair implements Comparable<pair> {
		int x;
		int y;
		int z;
		int idx;

		public pair(int d, int u, int w, int in) {
			x = d;
			y = u;
			z = w;
			idx = in;
		}

		@Override
		public int compareTo(pair o) {
			// TODO Auto-generated method stub

			return z - o.z;
		}

	}

	static class pair2 implements Comparable<pair2> {
		int x;
		int y;
		int idx;

		public pair2(int d, int u, int in) {
			x = d;
			y = u;
			idx = in;
		}

		@Override
		public int compareTo(pair2 o) {
			// TODO Auto-generated method stub
			return y - o.y;

		}

	}

	static class pair1 implements Comparable<pair1> {
		int x;
		int idx;

		public pair1(int d, int w) {
			x = d;
			idx = w;
		}

		@Override
		public int compareTo(pair1 o) {
			// TODO Auto-generated method stub

			return x - o.x;
		}

	}

}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UncheckedIOException;
import java.nio.charset.Charset;
import java.util.StringTokenizer;
import java.io.Writer;
import java.io.OutputStreamWriter;
import java.io.BufferedReader;
import java.util.Comparator;
import java.io.InputStream;

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

    static class C2BalancedRemovalsHarder {
        public void solve(int testNumber, LightScanner in, LightWriter out) {
            // out.setBoolLabel(LightWriter.BoolLabel.YES_NO_FIRST_UP);
            int n = in.ints();
            C2BalancedRemovalsHarder.P[] ps = new C2BalancedRemovalsHarder.P[n];
            for (int i = 0; i < n; i++) {
                ps[i] = new C2BalancedRemovalsHarder.P(i + 1, in.ints(), in.ints(), in.ints());
            }
            IntroSort.sort(ps);
            for (int i = 1; i < n; i++) {
                if (ps[i - 1].x == ps[i].x && ps[i - 1].y == ps[i].y) {
                    out.ans(ps[i - 1].index).ans(ps[i].index).ln();
                    ps[i - 1].used = ps[i].used = true;
                    i++;
                }
            }
            {
                int cur = 0;
                for (int i = 0; i < n; i++) {
                    if (ps[i].used) continue;
                    ps[cur++] = ps[i];
                }
                n = cur;
            }

            for (int i = 1; i < n; i++) {
                if (ps[i - 1].x == ps[i].x) {
                    out.ans(ps[i - 1].index).ans(ps[i].index).ln();
                    ps[i - 1].used = ps[i].used = true;
                    i++;
                }
            }
            {
                int cur = 0;
                for (int i = 0; i < n; i++) {
                    if (ps[i].used) continue;
                    ps[cur++] = ps[i];
                }
                n = cur;
            }

            for (int i = 0; i < n; i += 2) {
                out.ans(ps[i].index).ans(ps[i + 1].index).ln();
            }
        }

        private static class P implements Comparable<C2BalancedRemovalsHarder.P> {
            int index;
            int x;
            int y;
            int z;
            boolean used = false;

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

            public int compareTo(C2BalancedRemovalsHarder.P 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 interface Verified {
    }

    static class LightWriter implements AutoCloseable {
        private final Writer out;
        private boolean autoflush = false;
        private boolean breaked = true;

        public LightWriter(Writer out) {
            this.out = out;
        }

        public LightWriter(OutputStream out) {
            this(new BufferedWriter(new OutputStreamWriter(out, Charset.defaultCharset())));
        }

        public LightWriter print(char c) {
            try {
                out.write(c);
                breaked = false;
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
            return this;
        }

        public LightWriter print(String s) {
            try {
                out.write(s, 0, s.length());
                breaked = false;
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
            return this;
        }

        public LightWriter ans(String s) {
            if (!breaked) {
                print(' ');
            }
            return print(s);
        }

        public LightWriter ans(int i) {
            return ans(Integer.toString(i));
        }

        public LightWriter ln() {
            print(System.lineSeparator());
            breaked = true;
            if (autoflush) {
                try {
                    out.flush();
                } catch (IOException ex) {
                    throw new UncheckedIOException(ex);
                }
            }
            return this;
        }

        public void close() {
            try {
                out.close();
            } catch (IOException ex) {
                throw new UncheckedIOException(ex);
            }
        }

    }

    static class HeapSort {
        private HeapSort() {
        }

        private static <T> void heapfy(T[] a, int low, int high, int i, T val, Comparator<? super T> comparator) {
            int child = 2 * i - low + 1;
            while (child < high) {
                if (child + 1 < high && comparator.compare(a[child], a[child + 1]) < 0) {
                    child++;
                }
                if (comparator.compare(val, a[child]) >= 0) {
                    break;
                }
                a[i] = a[child];
                i = child;
                child = 2 * i - low + 1;
            }
            a[i] = val;
        }

        static <T> void sort(T[] a, int low, int high, Comparator<T> comparator) {
            for (int p = (high + low) / 2 - 1; p >= low; p--) {
                heapfy(a, low, high, p, a[p], comparator);
            }
            while (high > low) {
                high--;
                T pval = a[high];
                a[high] = a[low];
                heapfy(a, low, high, low, pval, comparator);
            }
        }

    }

    static final class ArrayUtil {
        private ArrayUtil() {
        }

        public static <T> void swap(T[] a, int x, int y) {
            T t = a[x];
            a[x] = a[y];
            a[y] = t;
        }

    }

    static class IntroSort {
        private static int INSERTIONSORT_THRESHOLD = 16;

        private IntroSort() {
        }

        static <T> void sort(T[] a, int low, int high, int maxDepth, Comparator<T> comparator) {
            while (high - low > INSERTIONSORT_THRESHOLD) {
                if (maxDepth-- == 0) {
                    HeapSort.sort(a, low, high, comparator);
                    return;
                }
                int cut = QuickSort.step(a, low, high, comparator);
                sort(a, cut, high, maxDepth, comparator);
                high = cut;
            }
            InsertionSort.sort(a, low, high, comparator);
        }

        public static <T> void sort(T[] a, Comparator<T> comparator) {
            if (a.length <= INSERTIONSORT_THRESHOLD) {
                InsertionSort.sort(a, 0, a.length, comparator);
            } else {
                sort(a, 0, a.length, 2 * BitMath.msb(a.length), comparator);
            }
        }

        public static <T extends Comparable<T>> void sort(T[] a) {
            sort(a, Comparator.naturalOrder());
        }

    }

    static class QuickSort {
        private QuickSort() {
        }

        private static <T> void med(T[] a, int low, int x, int y, int z, Comparator<? super T> comparator) {
            if (comparator.compare(a[z], a[x]) < 0) {
                ArrayUtil.swap(a, low, x);
            } else if (comparator.compare(a[y], a[z]) < 0) {
                ArrayUtil.swap(a, low, y);
            } else {
                ArrayUtil.swap(a, low, z);
            }
        }

        static <T> int step(T[] a, int low, int high, Comparator<? super T> comparator) {
            int x = low + 1, y = low + (high - low) / 2, z = high - 1;
            if (comparator.compare(a[x], a[y]) < 0) {
                med(a, low, x, y, z, comparator);
            } else {
                med(a, low, y, x, z, comparator);
            }

            int lb = low + 1, ub = high;
            while (true) {
                while (comparator.compare(a[lb], a[low]) < 0) {
                    lb++;
                }
                ub--;
                while (comparator.compare(a[low], a[ub]) < 0) {
                    ub--;
                }
                if (lb >= ub) {
                    return lb;
                }
                ArrayUtil.swap(a, lb, ub);
                lb++;
            }
        }

    }

    static class InsertionSort {
        private InsertionSort() {
        }

        static <T> void sort(T[] a, int low, int high, Comparator<? super T> comparator) {
            for (int i = low; i < high; i++) {
                for (int j = i; j > low && comparator.compare(a[j - 1], a[j]) > 0; j--) {
                    ArrayUtil.swap(a, j - 1, j);
                }
            }
        }

    }

    static class LightScanner {
        private BufferedReader reader = null;
        private StringTokenizer tokenizer = null;

        public LightScanner(InputStream in) {
            reader = new BufferedReader(new InputStreamReader(in));
        }

        public String string() {
            if (tokenizer == null || !tokenizer.hasMoreTokens()) {
                try {
                    tokenizer = new StringTokenizer(reader.readLine());
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
            return tokenizer.nextToken();
        }

        public int ints() {
            return Integer.parseInt(string());
        }

    }

    static final class BitMath {
        private BitMath() {
        }

        public static int count(int v) {
            v = (v & 0x55555555) + ((v >> 1) & 0x55555555);
            v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
            v = (v & 0x0f0f0f0f) + ((v >> 4) & 0x0f0f0f0f);
            v = (v & 0x00ff00ff) + ((v >> 8) & 0x00ff00ff);
            v = (v & 0x0000ffff) + ((v >> 16) & 0x0000ffff);
            return v;
        }

        public static int msb(int v) {
            if (v == 0) {
                throw new IllegalArgumentException("Bit not found");
            }
            v |= (v >> 1);
            v |= (v >> 2);
            v |= (v >> 4);
            v |= (v >> 8);
            v |= (v >> 16);
            return count(v) - 1;
        }

    }
}


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.util.*;
import java.lang.Math;

public class Solution {
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static StringTokenizer st;
	static long mod = 1000000000+7;

	public static void main(String[] args) throws IOException {
 	    // Read inputs
//  	    st = new StringTokenizer(br.readLine());
//  		int q = Integer.parseInt(st.nextToken());
//  		for(int i = 0; i<q; i++){
		    solve();
//  		}
	}

	public static void solve() throws IOException {
	    // Read inputs
	    st = new StringTokenizer(br.readLine());
		int n = Integer.parseInt(st.nextToken());
	    int[][] a = new int[n][4];

	    for(int i = 0; i<n; i++) {
            st = new StringTokenizer(br.readLine());
	        a[i][0] = Integer.parseInt(st.nextToken());
	        a[i][1] = Integer.parseInt(st.nextToken());
	        a[i][2] = Integer.parseInt(st.nextToken());
	        a[i][3] = i;
	    }
	    Arrays.sort(a,  new Comparator<int[]>() {
            public int compare(int[] o1, int[] o2) {
                if(o1[0] != o2[0]) return o1[0]-o2[0];
                else if(o1[1] != o2[1]) return o1[1]-o2[1];
                else return o1[2]-o2[2];
            }
        });
        int[] next = new int[n];
        int start = -1;
        int prev = 0;
        // Clear lines
	    for(int i = 0; i< n; i++ ) {
	        if(i<n-1 && isLine(a[i],a[i+1])){
	            System.out.println((a[i][3]+1) + " " + (a[i+1][3]+1));
	            i++;
	        } else {
	            if (start == -1) { start = i; prev = start; }
	            else { next[prev] = i; prev = i; }
	        }
	    }
	    if(start == -1) return;

	    // Clear squares
	    int next2 = start;
	    start = -1;
	    while (next2<n){
	        next[prev] = 0;
	        if(next[next2] != 0 && isSquare(a[next2],a[next[next2]])){
	            System.out.println((a[next2][3]+1) + " " + (a[next[next2]][3]+1));
	            next2 = next[next[next2]];
	        } else {
	            if (start == -1) {
	                start = next2;
	                prev = start;

	            }
	            else {
	                next[prev] = next2;
	                prev = next2;

	            }
	            next2 = next[next2];
	        }
            if(next2 == 0) break;
	    }
	    if(start == -1) return;

	    next2 = start;
	    while (next2<n){
            System.out.println((a[next2][3]+1) + " " + (a[next[next2]][3]+1));
            next2 = next[next[next2]];
            if(next2 == 0) return;
	    }
	}

	private static boolean isLine(int[] a, int[] b) {
	    return (a[0] == b[0] && a[1]==b[1]);
	}
	private static boolean isSquare(int[] a, int[] b) {
	    return (a[0] == b[0]);
	}
}


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

public class C {
    class Point implements Comparable<Point> {
        int id, x, y, z;
        boolean removed;

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

        @Override
        public String toString() {
            return "Point{" +
                    "id=" + id +
                    ", x=" + x +
                    ", y=" + y +
                    ", z=" + z +
                    '}';
        }

        @Override
        public int compareTo(Point p) {
            if (x != p.x) {
                return x - p.x;
            }
            if (y != p.y) {
                return y - p.y;
            }
            return z - p.z;
        }
    }
    boolean DEBUG = false;

    void solve() throws IOException {
        int n = nextInt();
        Point[] ps = new Point[n];
        for (int i = 0; i < n; i++) {
            ps[i] = new Point(i + 1, nextInt(), nextInt(), nextInt());
        }
        Arrays.sort(ps);
        for (int i = 0; i < n - 1; i++) {
            if (ps[i].x == ps[i + 1].x && ps[i].y == ps[i + 1].y) {
                ps[i].removed = true;
                ps[i + 1].removed = true;
                out.println(ps[i].id + " " + ps[i + 1].id);
                if (DEBUG) System.err.println(ps[i] + " " + ps[i+1]);
                i++;
            }
        }
        if (DEBUG) System.err.println("EO XY");
        ps = cleanup(ps);
        for (int i = 0; i < ps.length - 1; i++) {
            if (ps[i].x == ps[i + 1].x) {
                ps[i].removed = true;
                ps[i + 1].removed = true;
                out.println(ps[i].id + " " + ps[i + 1].id);
                if (DEBUG) System.err.println(ps[i] + " " + ps[i+1]);
                i++;
            }
        }
        if (DEBUG) System.err.println("EO X");
        ps = cleanup(ps);
        for (int i = 0; i < ps.length - 1; i++) {
            ps[i].removed = true;
            ps[i + 1].removed = true;
            out.println(ps[i].id + " " + ps[i + 1].id);
            if (DEBUG) System.err.println(ps[i] + " " + ps[i+1]);
            i++;
        }
    }

    Point[] cleanup(Point[] ps) {
        ArrayList<Point> tmp = new ArrayList<>();
        for (Point p : ps) {
            if (!p.removed) {
                tmp.add(p);
            }
        }
        Point[] np = new Point[tmp.size()];
        for (int i = 0; i < np.length; i++) {
            np[i] = tmp.get(i);
        }
        return np;
    }

    String nextToken() throws IOException {
        while (st == null || !st.hasMoreTokens()) {
            st = new StringTokenizer(br.readLine());
        }
        return st.nextToken();
    }

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

    StringTokenizer st;
    BufferedReader br;
    PrintWriter out;

    void run() throws IOException {
        br = new BufferedReader(new InputStreamReader(System.in));
        out = new PrintWriter(System.out);
        solve();
        out.close();
    }

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

}


import java.io.OutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.InputMismatchException;
import java.io.IOException;
import java.util.Comparator;
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;
        FastReader in = new FastReader(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, FastReader in, PrintWriter out) {
            int n = in.nextInt();
            int[][] p = new int[n][4];
            for (int i = 0; i < n; ++i) {
                for (int j = 0; j < 3; ++j) {
                    p[i][j] = in.nextInt();
                }
                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[] used = new boolean[n];
            for (int i = 0; i < n; ++i) {
                if (!used[i]) {
                    int j = i + 1;
                    while (used[j]) ++j;
                    int index = 0;
                    while (p[i][index] == p[j][index]) ++index;
                    int k = j;
                    while (k < n && p[k][index] == p[j][index]) ++k;
                    int res = Integer.MAX_VALUE;
                    int ind = -1;
                    for (int t = j; t < k; ++t) {
                        if (!used[t]) {
                            int cur = 0;
                            for (int l = 0; l < 3; ++l) cur += Math.abs(p[i][l] - p[t][l]);
                            if (cur < res) {
                                res = cur;
                                ind = t;
                            }
                        }
                    }
                    used[i] = used[ind] = true;
                    out.println(p[i][3] + " " + p[ind][3]);
                }
            }
        }

    }

    static class FastReader {
        private InputStream stream;
        private byte[] buf = new byte[8192];
        private int curChar;
        private int pnumChars;

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

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

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

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

    }
}