#include <bits/stdc++.h>
using namespace std;
int n;
struct XYZ {
  int id;
  int x, y, z;
  XYZ() {}
  XYZ(int _id) { id = _id; }
};
vector<XYZ> vec;
bool out[50004];
int main() {
  scanf("%d", &n);
  for (int i = 0; i < n; ++i) {
    XYZ _(i);
    scanf("%d%d%d", &_.x, &_.y, &_.z);
    vec.push_back(_);
  }
  map<int, vector<int>> X;
  for (auto &elem : vec) {
    if (X.find(elem.x) == X.end()) {
      vector<int> v;
      v.push_back(elem.id);
      X[elem.x] = v;
    } else {
      X[elem.x].push_back(elem.id);
    }
  }
  vector<int> no_no_no_more;
  for (auto &groupX : X) {
    if (((int)(groupX.second).size()) == 1) {
      continue;
    }
    map<int, vector<int>> Y;
    for (auto &id : groupX.second) {
      if (Y.find(vec[id].y) == Y.end()) {
        vector<int> v;
        v.push_back(id);
        Y[vec[id].y] = v;
      } else {
        Y[vec[id].y].push_back(id);
      }
    }
    vector<int> no_no_more;
    for (auto &groupY : Y) {
      if (((int)(groupY.second).size()) == 1) {
        continue;
      }
      map<int, vector<int>> Z;
      for (auto &iid : groupY.second) {
        if (Z.find(vec[iid].z) == Z.end()) {
          vector<int> v;
          v.push_back(iid);
          Z[vec[iid].z] = v;
        } else {
          Z[vec[iid].z].push_back(iid);
        }
      }
      vector<int> no_more;
      for (auto &groupZ : Z) {
        if (((int)(groupZ.second).size()) == 1) {
          continue;
        }
        while (((int)(groupZ.second).size()) >= 2) {
          int a = groupZ.second.back();
          groupZ.second.pop_back();
          int b = groupZ.second.back();
          groupZ.second.pop_back();
          printf("%d %d\n", a + 1, b + 1);
          out[a] = out[b] = 1;
        }
        if (((int)(groupZ.second).size()) == 0) {
          no_more.push_back(groupZ.first);
        }
      }
      for (auto &no : no_more) {
        Z.erase(no);
      }
      while (((int)(Z).size()) >= 2) {
        auto a = Z.begin();
        auto b = next(a);
        while (((int)(a->second).size()) > 1 and out[a->second.back()]) {
          a->second.pop_back();
        }
        while (((int)(b->second).size()) > 1 and out[b->second.back()]) {
          b->second.pop_back();
        }
        printf("%d %d\n", a->second.back() + 1, b->second.back() + 1);
        out[a->second.back()] = out[b->second.back()] = 1;
        Z.erase(a);
        Z.erase(b);
      }
      if (((int)(Z).size()) == 0) {
        no_no_more.push_back(groupY.first);
      }
    }
    for (auto &no : no_no_more) {
      Y.erase(no);
    }
    while (((int)(Y).size()) >= 2) {
      auto a = Y.begin();
      auto b = next(a);
      while (((int)(a->second).size()) > 1 and out[a->second.back()]) {
        a->second.pop_back();
      }
      while (((int)(b->second).size()) > 1 and out[b->second.back()]) {
        b->second.pop_back();
      }
      printf("%d %d\n", a->second.back() + 1, b->second.back() + 1);
      out[a->second.back()] = out[b->second.back()] = 1;
      Y.erase(a);
      Y.erase(b);
    }
    if (((int)(Y).size()) == 0) {
      no_no_no_more.push_back(groupX.first);
    } else {
    }
  }
  for (auto &no : no_no_no_more) {
    X.erase(no);
  }
  while (((int)(X).size()) >= 2) {
    auto a = X.begin();
    auto b = next(a);
    while (((int)(a->second).size()) > 1 and out[a->second.back()]) {
      a->second.pop_back();
    }
    while (((int)(b->second).size()) > 1 and out[b->second.back()]) {
      b->second.pop_back();
    }
    printf("%d %d\n", a->second.back() + 1, b->second.back() + 1);
    out[a->second.back()] = out[b->second.back()] = 1;
    X.erase(a);
    X.erase(b);
  }
  if (((int)(X).size()) != 0) {
    exit(1);
  }
  return (0);
}
