#include <bits/stdc++.h>
#pragma GCC optimize("O3")
#pragma GCC optimize("unroll-loops")
using namespace std;
int main() {
  ios::sync_with_stdio(false);
  cin.tie(0);
  int n;
  cin >> n;
  vector<tuple<int, int, int, int> > v(n);
  vector<int> used(n, 0);
  for (int i = 0; i < n; i++) {
    int a, b, c;
    cin >> a >> b >> c;
    v[i] = make_tuple(a, b, c, i);
  }
  sort(v.begin(), v.end());
  map<pair<int, int>, vector<int> > m;
  for (int i = 0; i < n; i++) {
    pair<int, int> t = {get<0>(v[i]), get<1>(v[i])};
    if (m.find(t) == m.end()) m[t] = vector<int>();
    m[t].push_back(i);
  }
  for (auto t : m) {
    for (int i = 0; i + 1 < t.second.size(); i += 2) {
      cout << get<3>(v[t.second[i]]) + 1 << " "
           << get<3>(v[t.second[i + 1]]) + 1 << "\n";
      used[get<3>(v[t.second[i]])] = 1;
      used[get<3>(v[t.second[i + 1]])] = 1;
    }
  }
  vector<tuple<int, int, int, int> > v1;
  for (int i = 0; i < v.size(); i++) {
    if (used[get<3>(v[i])] == 0) v1.push_back(v[i]);
  }
  map<int, vector<int> > m1;
  for (int i = 0; i < v1.size(); i++) {
    if (m1.find(get<0>(v1[i])) == m1.end()) {
      m1[get<0>(v1[i])] = vector<int>();
    }
    m1[get<0>(v1[i])].push_back(i);
  }
  for (auto t : m1) {
    for (int i = 0; i + 1 < t.second.size(); i += 2) {
      cout << get<3>(v1[t.second[i]]) + 1 << " "
           << get<3>(v1[t.second[i + 1]]) + 1 << "\n";
      used[get<3>(v1[t.second[i]])] = 1;
      used[get<3>(v1[t.second[i + 1]])] = 1;
    }
  }
  vector<tuple<int, int, int, int> > v2;
  for (int i = 0; i < v1.size(); i++) {
    if (used[get<3>(v1[i])] == 0) {
      v2.push_back(v1[i]);
    }
  }
  for (int i = 0; i < v2.size(); i += 2) {
    cout << get<3>(v2[i]) + 1 << " " << get<3>(v2[i + 1]) + 1 << "\n";
  }
}
