#include <bits/stdc++.h>
using namespace std;
unsigned seed = chrono::system_clock::now().time_since_epoch().count();
mt19937 Rand(seed);
uniform_int_distribution<long long> range(0, 1ll << 32);
inline void ucin() {
  ios::sync_with_stdio(0);
  cin.tie(0);
}
template <class T>
inline void chkmax(T &x, const T &y) {
  if (x < y) x = y;
}
template <class T>
inline void chkmin(T &x, const T &y) {
  if (x > y) x = y;
}
inline long long sqr(long long x) { return x * x; }
inline long long cub(long long x) { return x * x * x; }
const int N = 2e5 + 10;
const int mod = 1e9 + 7;
int qpow(int a, int b) {
  int ans = 1;
  for (; b >= 1; b >>= 1, a = (long long)a * a % mod)
    if (b & 1) ans = (long long)ans * a % mod;
  return ans;
}
struct node {
  int x, y, z, id;
  bool same2D(node b) { return x == b.x && y == b.y; }
  bool same1D(node b) { return x == b.x; }
  bool operator<(const node b) const {
    if (x ^ b.x) return x < b.x;
    if (y ^ b.y) return y < b.y;
    if (z ^ b.z) return z < b.z;
    return id < b.id;
  }
} a[N], st[N];
int n, top;
int main() {
  scanf("%d", &n);
  for (int i = 1; i <= n; ++i) {
    scanf("%d%d%d", &a[i].x, &a[i].y, &a[i].z);
    a[i].id = i;
  }
  sort(a + 1, a + n + 1);
  for (int i = 1; i <= n; ++i) {
    if (!top)
      st[++top] = a[i];
    else {
      if (a[i].same2D(st[top])) {
        printf("%d %d\n", a[i].id, st[top].id);
        top--;
      } else {
        st[++top] = a[i];
      }
    }
  }
  swap(top, n);
  swap(st, a);
  top = 0;
  for (int i = 1; i <= n; ++i) {
    if (!top)
      st[++top] = a[i];
    else {
      if (a[i].same1D(st[top])) {
        printf("%d %d\n", a[i].id, st[top].id);
        top--;
      } else {
        st[++top] = a[i];
      }
    }
  }
  for (int i = 1; i <= top - 1; i += 2) {
    printf("%d %d\n", st[i].id, st[i + 1].id);
  }
  return 0;
}
