This is a harder version of the problem. In this version, $n \le 50\,000$.

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

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

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

Find a way to remove all points in $\frac{n}{2}$ snaps.


-----Input-----

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

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

No two points coincide.


-----Output-----

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

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


-----Examples-----
Input
6
3 1 0
0 3 0
2 2 0
1 0 0
1 3 0
0 1 0

Output
3 6
5 1
2 4

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

Output
4 5
1 6
2 7
3 8



-----Note-----

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

Difficulty: HARD

Solutions:
n = int(input())
sentinel = [(10 ** 9, 10 ** 9, 10 ** 9)]
points = sorted((tuple(map(int, input().split())) + (i,) for i in range(1, n + 1))) + sentinel
ans = []
rem = []
i = 0
while i < n:
	if points[i][:2] == points[i + 1][:2]:
		ans.append(f'{points[i][-1]} {points[i + 1][-1]}')
		i += 2
	else:
		rem.append(i)
		i += 1
rem += [n]
rem2 = []
n = len(rem)
i = 0
while i < n - 1:
	if points[rem[i]][0] == points[rem[i + 1]][0]:
		ans.append(f'{points[rem[i]][-1]} {points[rem[i + 1]][-1]}')
		i += 2
	else:
		rem2.append(points[rem[i]][-1])
		i += 1
print(*ans, sep='\n')
for i in range(0, len(rem2), 2):
	print(rem2[i], rem2[i + 1])


from collections import OrderedDict

def SortPoint(p, ids, k, D):
	if k == D:
		return ids[0]
	maps = OrderedDict()
	for i in ids:
		if p[i][k] not in maps:
			maps[p[i][k]] = []
		maps[p[i][k]].append(i)
	a = []
	for i in sorted(maps.keys()):
		cnt = SortPoint(p, maps[i], k + 1, D)
		if cnt != -1:
			a.append(cnt)
	for i in range(0, len(a), 2):
		if i + 1 < len(a):
			print(a[i] + 1, a[i + 1] + 1)
	return -1 if len(a) % 2 == 0 else a[-1]

def solver():
	n = int(input())
	p = []
	ids = [i for i in range(n)]
	for i in range(n):
		point = list(map(int, input().split()))
		p.append(point)
	SortPoint(p, ids, 0, 3)

def main():
	while 1:
		try:
			solver()
		except EOFError:
			break

def __starting_point():
	main()
__starting_point()


n = int(input())
p = [list(map(int, input().split())) + [i] for i in range(n)] + [[10 ** 9, 10 ** 9, 10 ** 9]]
p.sort(key=lambda x: tuple(x[:-1]))
rem = []
i = 0
while i < n:
	if p[i][:2] == p[i + 1][:2]:
		print(p[i][-1] + 1, p[i + 1][-1] + 1)
		i += 2
	else:
		rem.append(p[i])
		i += 1
rem += [p[n]]
rem2 = []
n = len(rem)
i = 0
while i < n - 1:
	if rem[i][0] == rem[i + 1][0]:
		print(rem[i][-1] + 1, rem[i + 1][-1] + 1)
		i += 2
	else:
		rem2.append(rem[i])
		i += 1
for i in range(0, len(rem2), 2):
	print(rem2[i][-1] + 1, rem2[i + 1][-1] + 1)


n = int(input())
points = []
for i in range(n):
	points.append(list(map(int, input().split())))
	points[i].append(i + 1)
points.sort()
rem = []
i = 0
while i < len(points):
	if i < len(points) - 1 and points[i][:2] == points[i + 1][:2]:
		print(points[i][3], points[i + 1][3])
		i += 2
	else:
		rem.append(points[i])
		i += 1
points = list(rem)
rem = []
i = 0
while i < len(points):
	if i < len(points) - 1 and points[i][0] == points[i + 1][0]:
		print(points[i][3], points[i + 1][3])
		i += 2
	else:
		rem.append(points[i])
		i += 1
points = list(rem)
rem = []
i = 0
while i < len(points) - 1:
	print(points[i][3], points[i + 1][3])
	i += 2


n = int(input())
coordinates = []
for i in range(n):
	coordinates.append([int(i) for i in input().split()])
	coordinates[i].append(i + 1)
coordinates.sort()
remaining = []
i = 0
while i < len(coordinates):
	if i < len(coordinates) - 1 and coordinates[i][:2] == coordinates[i + 1][:2]:
		print(coordinates[i][3], coordinates[i + 1][3])
		i += 2
	else:
		remaining.append(coordinates[i])
		i += 1
coordinates = list(remaining)
remaining = []
i = 0
while i < len(coordinates):
	if i < len(coordinates) - 1 and coordinates[i][0] == coordinates[i + 1][0]:
		print(coordinates[i][3], coordinates[i + 1][3])
		i += 2
	else:
		remaining.append(coordinates[i])
		i += 1
coordinates = list(remaining)
for i in range(int(len(coordinates)) // 2):
	print(coordinates[2 * i][3], coordinates[2 * i + 1][3])


n = int(input())
coord = []
for i in range(n):
	coord.append(tuple(list(map(int, input().split())) + [i + 1]))
coord.sort()

def odind(l):
	l.sort()
	for i in range(len(l) // 2):
		print(l[2 * i][-1], l[2 * i + 1][-1])
	if len(l) % 2 == 0:
		return []
	return l[-1]

def dvad(l):
	cx = {}
	ans1 = []
	for (x, y, n) in l:
		if x in cx:
			cx[x].append([y, n])
		else:
			cx[x] = [[y, n]]
	for (x, j) in cx.items():
		v = odind(j)
		if len(v) != 0:
			ans1.append(tuple([x] + v))
	ans1.sort()
	for i in range(len(ans1) // 2):
		print(ans1[2 * i][-1], ans1[2 * i + 1][-1])
	if len(ans1) % 2 == 0:
		return []
	return ans1[-1]

def trid(l):
	cx = {}
	ans1 = []
	for (x, y, z, n) in l:
		if x in cx:
			cx[x].append((y, z, n))
		else:
			cx[x] = [(y, z, n)]
	for (x, j) in cx.items():
		v = list(dvad(j))
		if len(v) != 0:
			ans1.append(tuple([x] + v))
	ans1.sort()
	for i in range(len(ans1) // 2):
		print(ans1[2 * i][-1], ans1[2 * i + 1][-1])
trid(coord)


import sys
n = -1
data = []
for line in sys.stdin:
	if n == -1:
		n = int(line)
		continue
	data.append(tuple([int(i) for i in line.split()]))
	if len(data) == n:
		break
data = list(zip(data, list(range(1, n + 1))))
data.sort()
data2 = []
removedLast = False
for i in range(n):
	if removedLast:
		removedLast = False
		continue
	if i + 1 < n and data[i][0][0] == data[i + 1][0][0] and (data[i][0][1] == data[i + 1][0][1]):
		print('%d %d' % (data[i][1], data[i + 1][1]))
		removedLast = True
	else:
		data2.append(data[i])
data3 = []
removedLast = False
for i in range(len(data2)):
	if removedLast:
		removedLast = False
		continue
	if i + 1 < len(data2) and data2[i][0][0] == data2[i + 1][0][0]:
		print('%d %d' % (data2[i][1], data2[i + 1][1]))
		removedLast = True
	else:
		data3.append(data2[i])
for i in range(0, len(data3), 2):
	print('%d %d' % (data3[i][1], data3[i + 1][1]))


import sys
import os
from io import IOBase, BytesIO

def solve(n, points):
	for i in range(n):
		points[i].append(i + 1)
	points.sort()
	points += [(float('inf'),) * 3]
	rem = []
	i = 0
	while i < n:
		if points[i][:2] == points[i + 1][:2]:
			print(points[i][3], points[i + 1][3])
			i += 2
		else:
			rem.append(i)
			i += 1
	rem.append(n)
	rem2 = []
	i = 0
	while i < len(rem) - 1:
		if points[rem[i]][0] == points[rem[i + 1]][0]:
			print(points[rem[i]][3], points[rem[i + 1]][3])
			i += 2
		else:
			rem2.append(points[rem[i]][3])
			i += 1
	for i in range(0, len(rem2), 2):
		print(rem2[i], rem2[i + 1])

def main():
	n = int(input())
	points = [[int(c) for c in input().split()] for _ in range(n)]
	solve(n, points)
BUFSIZE = 8192

class FastIO(BytesIO):
	newlines = 0

	def __init__(self, file):
		self._file = file
		self._fd = file.fileno()
		self.writable = 'x' in file.mode or 'w' in file.mode
		self.write = super(FastIO, self).write if self.writable else None

	def _fill(self):
		s = os.read(self._fd, max(os.fstat(self._fd).st_size, BUFSIZE))
		self.seek((self.tell(), self.seek(0, 2), super(FastIO, self).write(s))[0])
		return s

	def read(self):
		while self._fill():
			pass
		return super(FastIO, self).read()

	def readline(self):
		while self.newlines == 0:
			s = self._fill()
			self.newlines = s.count(b'\n') + (not s)
		self.newlines -= 1
		return super(FastIO, self).readline()

	def flush(self):
		if self.writable:
			os.write(self._fd, self.getvalue())
			(self.truncate(0), self.seek(0))

class IOWrapper(IOBase):

	def __init__(self, file):
		self.buffer = FastIO(file)
		self.flush = self.buffer.flush
		self.writable = self.buffer.writable
		self.write = lambda s: self.buffer.write(s.encode('ascii'))
		self.read = lambda : self.buffer.read().decode('ascii')
		self.readline = lambda : self.buffer.readline().decode('ascii')
(sys.stdin, sys.stdout) = (IOWrapper(sys.stdin), IOWrapper(sys.stdout))

def input():
	return sys.stdin.readline().rstrip('\r\n')
main()


n = int(input())
a = [tuple(map(int, input().split())) for _ in range(n)]
s = {a[q]: q + 1 for q in range(n)}
a.sort()
(q, q1) = (0, 1)
(d, d1, d2) = ([[[a[0]]]], [], [])
while q1 < n:
	while q1 < n and a[q][0] == a[q1][0]:
		while q1 < n and a[q][1] == a[q1][1]:
			d[-1][-1].append(a[q1])
			q1 += 1
		if q1 < n and a[q][0] == a[q1][0]:
			d[-1].append([a[q1]])
			q = q1
			q1 += 1
	if q1 < n:
		d.append([[a[q1]]])
		q = q1
		q1 += 1
for q in range(len(d)):
	for q1 in range(len(d[q])):
		for q2 in range(1, len(d[q][q1]), 2):
			print(s[d[q][q1][q2 - 1]], s[d[q][q1][q2]])
		if len(d[q][q1]) % 2 == 1:
			d[q][q1] = d[q][q1][-1]
		else:
			d[q][q1] = -1
for q in range(len(d)):
	d1.append([])
	for q1 in range(len(d[q])):
		if d[q][q1] != -1:
			d1[-1].append(d[q][q1])
for q in range(len(d1)):
	for q1 in range(1, len(d1[q]), 2):
		print(s[d1[q][q1 - 1]], s[d1[q][q1]])
	if len(d1[q]) % 2 == 1:
		d2.append(d1[q][-1])
for q in range(1, len(d2), 2):
	print(s[d2[q - 1]], s[d2[q]])


import sys
import math
input = sys.stdin.readline
n = int(input())
arr = []
for i in range(n):
	arr.append(list(map(int, input().split())))
	arr[-1].append(i + 1)
ans = []
arr.sort()
done = [0] * n
i = 0
j = 1
while i < n and j < n:
	if arr[i][0] == arr[j][0] and arr[i][1] == arr[j][1]:
		ans.append([arr[i][3], arr[j][3]])
		done[arr[i][3] - 1] += 1
		done[arr[j][3] - 1] += 1
		i += 2
		j += 2
	else:
		i += 1
		j += 1
arr1 = []
for i in range(n):
	if done[arr[i][3] - 1] == 0:
		arr1.append(arr[i])
n = len(arr1)
i = 0
j = 1
while i < n and j < n:
	if arr1[i][0] == arr1[j][0]:
		ans.append([arr1[i][3], arr1[j][3]])
		done[arr1[i][3] - 1] += 1
		done[arr1[j][3] - 1] += 1
		i += 2
		j += 2
	else:
		i += 1
		j += 1
arr2 = []
for i in range(n):
	if done[arr1[i][3] - 1] == 0:
		arr2.append(arr1[i])
n = len(arr2)
for i in range(n):
	if i % 2 == 0:
		ans.append([arr2[i][3], arr2[i + 1][3]])
for i in range(len(ans)):
	print(*ans[i])


from sys import stdin
input = stdin.readline
n = int(input())
l = [list(map(int, input().split())) for i in range(n)]
a = [[l[i], i + 1] for i in range(n)]
used = [0] * (n + 1)
a.sort()
d = {}
for i in a:
	d[i[0][0]] = []
for i in a:
	d[i[0][0]].append([i[0][1], i[0][2], i[1]])
for x in d:
	l = d[x]
	b = {}
	for i in l:
		b[i[0]] = []
	for i in l:
		b[i[0]].append([i[1], i[2]])
	for y in b:
		pom = 0
		while pom + 1 < len(b[y]):
			print(b[y][pom][1], b[y][pom + 1][1])
			used[b[y][pom][1]] = 1
			used[b[y][pom + 1][1]] = 1
			pom += 2
	c = []
	for gg in l:
		if used[gg[2]] == 0:
			c.append(gg)
	cc = [[c[gg][0], c[gg][2]] for gg in range(len(c))]
	g = 0
	while g < len(cc) - 1:
		print(cc[g][-1], cc[g + 1][-1])
		used[cc[g][-1]] = 1
		used[cc[g + 1][-1]] = 1
		g += 2
c = []
for i in range(0, n):
	if used[a[i][1]] == 0:
		c.append([a[i][0][0], a[i][1]])
c.sort()
i = 0
while i < len(c) - 1:
	print(c[i][1], c[i + 1][1])
	i += 2


n = int(input())
li = []
for i in range(n):
	(a, b, c) = map(int, input().split())
	li.append([a, b, c, i + 1])
li.sort()
i = 0
l = len(li)
a = []
while i < l:
	if i < l - 1 and li[i][:2] == li[i + 1][:2]:
		print(li[i][3], li[i + 1][3])
		i += 2
	else:
		a.append(li[i])
		i += 1
li = list(a)
l = len(li)
i = 0
a = []
while i < l:
	if i < l - 1 and li[i][0] == li[i + 1][0]:
		print(li[i][3], li[i + 1][3])
		i += 2
	else:
		a.append(li[i])
		i += 1
li = list(a)
l = len(li)
i = 0
while i < l - 1:
	print(li[i][3], li[i + 1][3])
	i += 2


from sys import stdin, stdout
n = int(stdin.readline())
l = []
for i in range(n):
	x = list(map(int, stdin.readline().split()))
	l.append([x, i + 1])
l.sort()
deleted = [0] * n
for i in range(n - 1):
	if deleted[i]:
		continue
	if l[i][0][0] == l[i + 1][0][0] and l[i][0][1] == l[i + 1][0][1]:
		print(l[i][1], l[i + 1][1])
		deleted[i] = deleted[i + 1] = 1
remaining = []
for i in range(n):
	if deleted[i] == 0:
		remaining.append(l[i])
del deleted
n = len(remaining)
deleted = [0] * n
for i in range(n - 1):
	if deleted[i]:
		continue
	if remaining[i][0][0] == remaining[i + 1][0][0]:
		print(remaining[i][1], remaining[i + 1][1])
		deleted[i] = deleted[i + 1] = 1
last = []
for i in range(n):
	if deleted[i] == 0:
		last.append(remaining[i])
for i in range(0, len(last), 2):
	print(last[i][1], last[i + 1][1])


import sys
from collections import defaultdict
readline = sys.stdin.readline
N = int(readline())
XYZ = [tuple(map(int, readline().split())) for _ in range(N)]
XYZI = [(x, y, z, i) for (i, (x, y, z)) in enumerate(XYZ, 1)]
XYZI.sort()
(X, Y, Z, IDX) = map(list, zip(*XYZI))
Di = defaultdict(list)
for i in range(N):
	(x, y, z, idx) = (X[i], Y[i], Z[i], IDX[i])
	Di[x].append((y, z, idx))
Ans = []
Ama = []
for L in Di.values():
	D2 = defaultdict(int)
	for (y, _, _) in L:
		D2[y] += 1
	pre = None
	st = None
	for (y, z, i) in L:
		if st is None and D2[y] == 1:
			if pre is not None:
				Ans.append((pre, i))
				pre = None
			else:
				pre = i
		elif st is not None:
			Ans.append((st, i))
			st = None
		else:
			st = i
		D2[y] -= 1
	if st:
		Ama.append(st)
	if pre:
		Ama.append(pre)
Le = len(Ama)
for i in range(Le // 2):
	Ans.append((Ama[2 * i], Ama[2 * i + 1]))
for a in Ans:
	sys.stdout.write('{} {}\n'.format(*a))


n = int(input())
coords = n * [-1]
coordsy = list()
coordsz = list()
for i in range(n):
	coords[i] = [int(i) for i in input().split()] + [i]
coords.sort()
i = 0
while i < len(coords) - 1:
	if coords[i][:2] == coords[i + 1][:2]:
		print(coords[i][3] + 1, coords[i + 1][3] + 1)
		i += 1
	else:
		coordsy.append(coords[i])
	i += 1
	if i == len(coords) - 1:
		coordsy.append(coords[i])
i = 0
while i < len(coordsy) - 1:
	if coordsy[i][:1] == coordsy[i + 1][:1]:
		print(coordsy[i][3] + 1, coordsy[i + 1][3] + 1)
		i += 1
	else:
		coordsz.append(coordsy[i])
	i += 1
	if i == len(coordsy) - 1:
		coordsz.append(coordsy[i])
for i in range(0, len(coordsz), 2):
	print(coordsz[i][3] + 1, coordsz[i + 1][3] + 1)


def solve(n, points):
	points = [p + (i,) for (i, p) in enumerate(points, 1)]
	points += [(10 ** 9,) * 3]
	points.sort()
	ans = []
	rem = []
	i = 0
	while i < n:
		if points[i][:2] == points[i + 1][:2]:
			ans.append((points[i][-1], points[i + 1][-1]))
			i += 2
		else:
			rem.append(i)
			i += 1
	rem += [n]
	rem2 = []
	n = len(rem)
	i = 0
	while i < n - 1:
		if points[rem[i]][0] == points[rem[i + 1]][0]:
			ans.append((points[rem[i]][-1], points[rem[i + 1]][-1]))
			i += 2
		else:
			rem2.append(points[rem[i]][-1])
			i += 1
	for e in ans:
		print(*e)
	for i in range(0, len(rem2), 2):
		print(rem2[i], rem2[i + 1])

def main():
	n = int(input())
	points = [tuple((int(c) for c in input().split())) for _ in range(n)]
	solve(n, points)
main()


def solve(n, points):
	for i in range(n):
		points[i].append(i + 1)
	points.sort()
	points += [(float('inf'),) * 3]
	rem = []
	i = 0
	while i < n:
		if points[i][:2] == points[i + 1][:2]:
			print(points[i][3], points[i + 1][3])
			i += 2
		else:
			rem.append(i)
			i += 1
	rem.append(n)
	rem2 = []
	i = 0
	while i < len(rem) - 1:
		if points[rem[i]][0] == points[rem[i + 1]][0]:
			print(points[rem[i]][3], points[rem[i + 1]][3])
			i += 2
		else:
			rem2.append(points[rem[i]][3])
			i += 1
	for i in range(0, len(rem2), 2):
		print(rem2[i], rem2[i + 1])

def main():
	n = int(input())
	points = [[int(c) for c in input().split()] for _ in range(n)]
	solve(n, points)
main()


import sys
input = sys.stdin.readline
n = int(input())
P = [list(map(int, input().split())) + [i + 1] for i in range(n)]
P.sort(reverse=True)
P2 = []
while P:
	(x, y, z, w) = P.pop()
	if len(P) > 0 and x == P[-1][0] and (y == P[-1][1]):
		(x2, y2, z2, w2) = P.pop()
		print(w, w2)
	else:
		P2.append((x, y, z, w))
P = P2
P.sort(reverse=True)
P2 = []
while P:
	(x, y, z, w) = P.pop()
	if len(P) > 0 and x == P[-1][0]:
		(x2, y2, z2, w2) = P.pop()
		print(w, w2)
	else:
		P2.append((x, y, z, w))
P = P2
for i in range(0, len(P), 2):
	print(P[i][3], P[i + 1][3])


def solve(n, points):
	points = sorted((p + (i,) for (i, p) in enumerate(points, 1)))
	points += [(float('inf'),) * 3]
	rem = []
	i = 0
	while i < n:
		if points[i][:2] == points[i + 1][:2]:
			print(points[i][3], points[i + 1][3])
			i += 2
		else:
			rem.append(i)
			i += 1
	rem.append(n)
	rem2 = []
	i = 0
	while i < len(rem) - 1:
		if points[rem[i]][0] == points[rem[i + 1]][0]:
			print(points[rem[i]][3], points[rem[i + 1]][3])
			i += 2
		else:
			rem2.append(points[rem[i]][3])
			i += 1
	for i in range(0, len(rem2), 2):
		print(rem2[i], rem2[i + 1])

def main():
	n = int(input())
	points = [tuple((int(c) for c in input().split())) for _ in range(n)]
	solve(n, points)
main()


n = int(input())
a = [[]]
for i in range(n):
	(x, y, z) = map(int, input().split())
	a.append([x, y, z])
from collections import defaultdict

def solve(lst, k):
	if k == 3:
		return lst[0]
	mp = defaultdict(list)
	for i in lst:
		mp[a[i][k]].append(i)
	ans = []
	p = []
	for i in mp:
		p.append(i)
	p.sort()
	for i in p:
		t = solve(mp[i], k + 1)
		if t != -1:
			ans.append(t)
	for i in range(0, len(ans) - 1, 2):
		print(ans[i], ans[i + 1])
	if len(ans) % 2:
		return ans[-1]
	return -1
solve([i for i in range(1, n + 1)], 0)


n = int(input())
coors = []
for i in range(n):
	coors.append(list(map(int, input().split())) + [i + 1])
coors = sorted(coors, key=lambda x: (x[0], x[1], x[2]))
left = []

def oneD(co, remain):
	if len(co) % 2 == 0:
		for i in range(0, len(co), 2):
			print(co[i][3], co[i + 1][3])
	else:
		remain.append(co[-1])
		for i in range(0, len(co) - 1, 2):
			print(co[i][3], co[i + 1][3])
	return remain

def twoD(co):
	if len(co) == 1:
		left.append(*co)
		return
	remain = []
	l = 1
	for i in range(len(co) - 1):
		if co[i][1] == co[i + 1][1]:
			l += 1
		else:
			remain = oneD(co[i + 1 - l:i + 1], remain)
			l = 1
	remain = oneD(co[len(co) - l:], remain)
	if len(remain) % 2 == 0:
		for i in range(0, len(remain), 2):
			print(remain[i][3], remain[i + 1][3])
	else:
		left.append(remain[-1])
		for i in range(0, len(remain) - 1, 2):
			print(remain[i][3], remain[i + 1][3])
l = 1
for i in range(n - 1):
	if coors[i][0] == coors[i + 1][0]:
		l += 1
	else:
		twoD(coors[i + 1 - l:i + 1])
		l = 1
twoD(coors[n - l:])
for i in range(0, len(left), 2):
	print(left[i][3], left[i + 1][3])


n = int(input())
points = []
for i in range(n):
	(x, y, z) = map(int, input().split())
	points.append((x, y, z, i + 1))
points.sort()
x_points = {}
for p in points:
	(x, y, z, i) = p
	if x in x_points:
		x_points[x].append(p)
	else:
		x_points[x] = [p]
x_to_remove = []
for (x, pp) in x_points.items():
	if len(pp) >= 2:
		y_points = {}
		for p in pp:
			(x, y, z, i) = p
			if y in y_points:
				y_points[y].append(p)
			else:
				y_points[y] = [p]
		y_to_remove = []
		for (y, ppp) in y_points.items():
			if len(ppp) >= 2:
				ppp.sort()
				for i in range(0, len(ppp) - 1, 2):
					print(ppp[i][3], ppp[i + 1][3])
				if len(ppp) % 2:
					y_points[y] = [ppp[-1]]
				else:
					y_to_remove.append(y)
		for y in y_to_remove:
			del y_points[y]
		indexes = [(ppp[0][3], y) for (y, ppp) in sorted(y_points.items())]
		for i in range(0, len(indexes) - 1, 2):
			print(indexes[i][0], indexes[i + 1][0])
		if len(indexes) % 2:
			x_points[x] = y_points[indexes[-1][1]]
		else:
			x_to_remove.append(x)
for x in x_to_remove:
	del x_points[x]
indexes = [pp[0][3] for (x, pp) in sorted(x_points.items())]
for i in range(0, len(indexes), 2):
	print(indexes[i], indexes[i + 1])


import sys
n = int(input())
d = [tuple([int(i) for i in sys.stdin.readline().split()]) for j in range(n)]
index = {}
for i in range(n):
	index[d[i]] = i + 1
d.sort()
untaken = []
dic = {}
i = 0
while i < n:
	first = d[i][0]
	if first not in dic:
		dic[first] = []
	dic[first].append((d[i][1], d[i][2]))
	i += 1
for dd in dic:
	e = dic[dd]
	nn = len(e)
	unt = []
	i = 0
	while i < nn:
		if i < nn - 1 and e[i][0] == e[i + 1][0]:
			sys.stdout.write(str(index[(dd,) + e[i]]) + ' ' + str(index[(dd,) + e[i + 1]]) + '\n')
			i += 2
		else:
			unt.append(i)
			i += 1
	for j in range(0, len(unt) - 1, 2):
		sys.stdout.write(str(index[(dd,) + e[unt[j]]]) + ' ' + str(index[(dd,) + e[unt[j + 1]]]) + '\n')
	if len(unt) & 1:
		untaken.append((dd,) + e[unt[-1]])
for i in range(0, len(untaken), 2):
	sys.stdout.write(str(index[untaken[i]]) + ' ' + str(index[untaken[i + 1]]) + '\n')