/**
 * code generated by JHelper
 * More info: https://github.com/AlexeyDmitriev/JHelper
 * @author
 */

#include <iostream>
#include <fstream>

#include <iostream>
#include <algorithm>
#include <queue>
#include <vector>
#include <set>
#include <map>
#include <cassert>
#include <functional>
#include <sstream>
#include <cmath>

#include <cstring>
#include <cstdio>
#ifndef ATCODER_DSU_HPP
#define ATCODER_DSU_HPP 1





namespace atcoder {

// Implement (union by size) + (path compression)
// Reference:
// Zvi Galil and Giuseppe F. Italiano,
// Data structures and algorithms for disjoint set union problems
struct dsu {
  public:
    dsu() : _n(0) {}
    explicit dsu(int n) : _n(n), parent_or_size(n, -1) {}

    int merge(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        int x = leader(a), y = leader(b);
        if (x == y) return x;
        if (-parent_or_size[x] < -parent_or_size[y]) std::swap(x, y);
        parent_or_size[x] += parent_or_size[y];
        parent_or_size[y] = x;
        return x;
    }

    bool same(int a, int b) {
        assert(0 <= a && a < _n);
        assert(0 <= b && b < _n);
        return leader(a) == leader(b);
    }

    int leader(int a) {
        assert(0 <= a && a < _n);
        if (parent_or_size[a] < 0) return a;
        return parent_or_size[a] = leader(parent_or_size[a]);
    }

    int size(int a) {
        assert(0 <= a && a < _n);
        return -parent_or_size[leader(a)];
    }

    std::vector<std::vector<int>> groups() {
        std::vector<int> leader_buf(_n), group_size(_n);
        for (int i = 0; i < _n; i++) {
            leader_buf[i] = leader(i);
            group_size[leader_buf[i]]++;
        }
        std::vector<std::vector<int>> result(_n);
        for (int i = 0; i < _n; i++) {
            result[i].reserve(group_size[i]);
        }
        for (int i = 0; i < _n; i++) {
            result[leader_buf[i]].push_back(i);
        }
        result.erase(
            std::remove_if(result.begin(), result.end(),
                           [&](const std::vector<int>& v) { return v.empty(); }),
            result.end());
        return result;
    }

  private:
    int _n;
    // root node: -1 * component size
    // otherwise: parent
    std::vector<int> parent_or_size;
};

}  // namespace atcoder

#endif  // ATCODER_DSU_HPP


using namespace std;

typedef pair<int, int> ii;
typedef long long int64;
typedef vector<int> vi;

#define pb push_back
#define mp make_pair
#define all(v) (v).begin(),(v).end()
#define sz(v) ((int)(v).size())

template<class T> T abs(T x) { return x >= 0 ? x : -x; }
template<class T> T sqr(T x) { return x * x; }

struct P {
    int ind, l;
    int x, y;
    P() {}
    P(int x, int y) : x(x), y(y) {}

    P operator-(const P& other) const {
        return P(x - other.x, y - other.y);
    }

    int64 cross(const P& other) const {
        return x * (int64) other.y - y * (int64) other.x;
    }
};

class TaskD {
public:
    vector<ii> doSolve(vector<P> a) {
        int n = sz(a);
        sort(all(a), [](const P& p, const P& q) {
            return ii(p.x, p.y) < ii(q.x, q.y);
        });
        int start = 0;
        while (start < sz(a) && a[start].l == a[start].ind) ++start;
        if (start >= sz(a)) {
            return {};
        }
        a = vector<P>(a.begin() + start, a.end());
        P o = a[0];
        sort(a.begin() + 1, a.end(), [&o](const P& p, const P& q) {
            P v1 = p - o;
            P v2 = q - o;
            int64 val = v1.cross(v2);
            return val < 0;
        });
        vector<ii> res;
        atcoder::dsu dsu(n);
        vector<P*> pos(n, nullptr);
        for (int i = 0; i < sz(a); ++i) {
            pos[a[i].ind] = &a[i];
        }
        vi u(n, 0);
        for (int i = 0; i < n; ++i) {
            if (pos[i] == nullptr) continue;
            P* cur = pos[i];
            if (cur == nullptr) continue;
            while (!u[cur->ind]) {
                int ind = cur->ind;
                u[ind] = 1;
                int y = cur->l;
                cur = pos[y];
                if (cur == nullptr) {
                    assert(cur != nullptr);
                }
                if (!dsu.same(ind, cur->ind))
                    dsu.merge(ind, cur->ind);
            }
        }
        for (int i = 1; i + 1 < sz(a); ++i) {
            if (!dsu.same(a[i].ind, a[i + 1].ind)) {
                res.pb(ii(a[i].ind, a[i + 1].ind));
                swap(a[i].l, a[i + 1].l);
                dsu.merge(a[i].ind, a[i + 1].ind);
            }
        }
        int r = dsu.leader(a[0].ind);
        for (int i = 0; i < sz(a); ++i) {
            if (r != dsu.leader(a[i].ind)) {
                assert(r == dsu.leader(a[i].ind));
            }
        }
        while (a[0].ind != a[0].l) {
            P* p = pos[a[0].l];
            res.pb(ii(a[0].ind, p->ind));
            swap(a[0].l, p->l);
        }
        for (P p : a)
            assert(p.ind == p.l);
        return res;
    }

	void solve(std::istream& in, std::ostream& out) {
//        {
//            int n = 10;
//            for (int it = 0; it < 100000; ++it) {
//                vi a;
//                for (int i = 0; i < n; ++i) {
//                    a.pb(i);
//                }
//                random_shuffle(all(a));
//                vector<P> v;
//                for (int i = 0; i < n; ++i) {
//                    P p(rand() % 100000, rand() % 100000);
//                    p.ind = i;
//                    p.l = a[i];
//                    v.pb(p);
//                }
//                doSolve(v);
//            }
//        }
	    int n;
	    in >> n;
	    vector<P> a(n);
	    for (int i = 0; i < n; ++i) {
	        a[i].ind = i;
            in >> a[i].x >> a[i].y >> a[i].l;
            --a[i].l;
        }

	    auto res = doSolve(a);
	    out << sz(res) << "\n";
	    for (ii e : res)
	        out << (e.first + 1) << " " << (e.second + 1) << "\n";
	}
};


int main() {
	TaskD solver;
	std::istream& in(std::cin);
	std::ostream& out(std::cout);
	solver.solve(in, out);
	return 0;
}
