#include <bits/stdc++.h>
#pragma GCC optimize("Ofast,no-stack-protector")
#pragma GCC target( \
    "sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("fast-math")
using namespace std;
const int INF = 1e9 + 9;
const long long MAXN = 25e4 + 7;
const long long MAXN1 = 21;
const long long MAXN2 = 1e6 + 7;
const long long MOD = 998244353;
const long long PW = 31;
const long long BLOCK = 317;
void solve();
mt19937 mt(1e9 + 7);
signed main() {
  srand('a' + 'l' + 'e' + 'x' + 'X' + '5' + '1' + '2');
  ios_base::sync_with_stdio(0);
  cin.tie(0);
  cout.tie(0);
  int q = 1;
  if (0) cin >> q;
  while (q--) solve();
}
bool used[MAXN];
struct Node {
  int x, y, z, id;
  Node() {}
  Node(int a, int b, int c = INF, int d = -INF) : x(a), y(b), z(c), id(d) {}
  bool operator<(Node other) {
    if (x != other.x) return x < other.x;
    if (y != other.y) return y < other.y;
    return z < other.z;
  }
};
int v[4 * MAXN];
void update(int vert, int l, int r, int id, int z) {
  if (l == r) {
    v[vert] = z;
    return;
  }
  int m = (l + r) / 2;
  if (id <= m)
    update(vert << 1, l, m, id, z);
  else
    update(vert << 1 | 1, m + 1, r, id, z);
  v[vert] = min(v[vert << 1], v[vert << 1 | 1]);
}
pair<int, int> get(int vert, int l, int r, int tl, int tr) {
  if (l == r) {
    return make_pair(v[vert], l);
  }
  if (l == tl && r == tr) {
    int m = (l + r) / 2;
    if (v[vert << 1] < v[vert << 1 | 1])
      return get(vert << 1, l, m, tl, min(m, tr));
    else
      return get(vert << 1 | 1, m + 1, r, max(tl, m + 1), tr);
  }
  int m = (l + r) / 2;
  if (tr <= m)
    return get(vert << 1, l, m, tl, tr);
  else if (tl > m)
    return get(vert << 1 | 1, m + 1, r, tl, tr);
  else
    return min(get(vert << 1, l, m, tl, m),
               get(vert << 1 | 1, m + 1, r, m + 1, tr));
}
int v2[4 * MAXN];
void update2(int vert, int l, int r, int id, int z) {
  if (l == r) {
    v2[vert] = z;
    return;
  }
  int m = (l + r) / 2;
  if (id <= m)
    update2(vert << 1, l, m, id, z);
  else
    update2(vert << 1 | 1, m + 1, r, id, z);
  v2[vert] = max(v2[vert << 1], v2[vert << 1 | 1]);
}
pair<int, int> get2(int vert, int l, int r, int tl, int tr) {
  if (l == r) {
    return make_pair(v2[vert], l);
  }
  if (l == tl && r == tr) {
    int m = (l + r) / 2;
    if (v2[vert << 1] < v2[vert << 1 | 1])
      return get2(vert << 1, l, m, tl, min(m, tr));
    else
      return get2(vert << 1 | 1, m + 1, r, max(tl, m + 1), tr);
  }
  int m = (l + r) / 2;
  if (tr <= m)
    return get2(vert << 1, l, m, tl, tr);
  else if (tl > m)
    return get2(vert << 1 | 1, m + 1, r, tl, tr);
  else
    return min(get2(vert << 1, l, m, tl, m),
               get2(vert << 1 | 1, m + 1, r, m + 1, tr));
}
bool cmp(Node a, Node b) { return a < b; }
void f(int a, int b, pair<int, int> c,
       map<int, map<int, set<pair<int, int>>>>& m) {
  m[a][b].erase(c);
  if (m[a][b].empty()) {
    m[a].erase(b);
  }
  if (m[a].empty()) {
    m.erase(a);
  }
}
void solve() {
  int n;
  cin >> n;
  vector<Node> v(n);
  map<int, map<int, set<pair<int, int>>>> m;
  for (int i = 0; i < n; ++i) {
    int a, b, c;
    cin >> a >> b >> c;
    m[a][b].emplace(c, i + 1);
  }
  while (!m.empty()) {
    int a = (*m.begin()).first;
    int b = (*(*m.begin()).second.begin()).first;
    auto c = *(*(*m.begin()).second.begin()).second.begin();
    f(a, b, c, m);
    auto p = m.lower_bound(a);
    auto p1 = m[(*p).first].upper_bound(b);
    if (p1 != m[(*p).first].begin()) --p1;
    auto p2 = m[(*p).first][(*p1).first].upper_bound(make_pair(c.first, INF));
    if (p2 != m[(*p).first][(*p1).first].begin()) --p2;
    cout << c.second << " " << (*p2).second << "\n";
    f((*p).first, (*p1).first, *p2, m);
  }
}
