#include "bits/stdc++.h"
#define _overload3(_1,_2,_3,name,...) name
#define _rep(i,n) repi(i,0,n)
#define repi(i,a,b) for(int i=int(a),i##_len=(b);i<i##_len;++i)
#define MSVC_UNKO(x) x
#define rep(...) MSVC_UNKO(_overload3(__VA_ARGS__,repi,_rep,_rep)(__VA_ARGS__))
#define all(c) c.begin(),c.end()
#define mp make_pair
#define write(x) cout<<(x)<<"\n"
using namespace std; typedef long long ll; typedef vector<int> vi; typedef vector<ll> vll;
template<class T>using vv = vector<vector<T>>; template<class T>auto vvec(int n, int m, T v) { return vv<T>(n, vector<T>(m, v)); }
template<class T>bool chmax(T& a, const T& b) { return a < b ? a = b, 1 : 0; }
template<class T>bool chmin(T& a, const T& b) { return b < a ? a = b, 1 : 0; }
constexpr int INF = 1 << 29, MOD = int(1e9) + 7; constexpr ll LINF = 1LL << 60;
struct aaa { aaa() { cin.tie(0); ios::sync_with_stdio(0); cout << fixed << setprecision(10); }; }aaaa;

struct UnionFind {
    vector<int> parent;
    vector<int> rank;
    int operator [](int x) { return find(x); }
    void init(int size) { parent.assign(size, -1); rank.assign(size, 0); }
    int find(int x) { return parent[x] < 0 ? x : (parent[x] = find(parent[x])); }
    bool same(int x, int y) { return find(x) == find(y); }
    int size(int x) { return -parent[find(x)]; }
    bool unite(int x, int y) {
        if ((x = find(x)) == (y = find(y))) return 0;
        return rank[x] = max(rank[x], rank[y] + 1), parent[x] += parent[y], parent[y] = x, 1;
    }
    bool uniteByRank(int x, int y) {
        if ((x = find(x)) == (y = find(y))) return 0;
        else if (rank[x] < rank[y]) swap(x, y); else if (rank[x] == rank[y]) rank[x]++;
        return parent[x] += parent[y], parent[y] = x, 1;
    }
    bool uniteBySize(int x, int y) {
        if ((x = find(x)) == (y = find(y))) return 0;
        else if (size(x) < size(y)) swap(x, y);
        return rank[x] = max(rank[x], rank[y] + 1), parent[x] += parent[y], parent[y] = x, 1;
    }
};


const int maxX = 1e5 + 1;
int N;
vi X, Y;
vector<pair<int, int>> xi, yi;
UnionFind uf;
vector<set<int>> xset, yset;
vector<bool> used;
vector<bool> xvisited, yvisited;

void dfs(int n, int p) {
    used[n] = true;
    uf.unite(p, n);
    xset[p].insert(X[n]);
    yset[p].insert(Y[n]);

    if (!xvisited[X[n]]) {
        xvisited[X[n]] = true;
        int s = lower_bound(all(xi), mp(X[n], 0)) - xi.begin();
        int t = upper_bound(all(xi), mp(X[n], INF)) - xi.begin();
        for (int i = s; i < t; ++i) {
            if (!used[xi[i].second]) {
                dfs(xi[i].second, p);
            }
        }
    }
    if (!yvisited[Y[n]]) {
        yvisited[Y[n]] = true;
        int s = lower_bound(all(yi), mp(Y[n], 0)) - yi.begin();
        int t = upper_bound(all(yi), mp(Y[n], INF)) - yi.begin();
        for (int i = s; i < t; ++i) {
            if (!used[yi[i].second]) {
                dfs(yi[i].second, p);
            }
        }
    }
}

int main() {
    cin >> N;
    X.resize(N); Y.resize(N);
    xi.reserve(N); yi.reserve(N);
    rep(i, N) {
        cin >> X[i] >> Y[i];
        xi.emplace_back(X[i], i);
        yi.emplace_back(Y[i], i);
    }
    sort(all(xi)); sort(all(yi));

    uf.init(N);
    xset.resize(N), yset.resize(N);
    used.resize(N);
    xvisited.resize(maxX); yvisited.resize(maxX);

    rep(i, N) {
        if (used[i]) continue;

        int nx = upper_bound(all(xi), mp(X[i], INF)) - lower_bound(all(xi), mp(X[i], 0));
        int ny = upper_bound(all(yi), mp(Y[i], INF)) - lower_bound(all(yi), mp(Y[i], 0));

        if (nx > 1 && ny > 1) dfs(i, i);
    }

    ll ans = 0;
    rep(i, N) {
        if (uf[i] == i) {
            int nx = upper_bound(all(xi), mp(X[i], INF)) - lower_bound(all(xi), mp(X[i], 0));
            int ny = upper_bound(all(yi), mp(Y[i], INF)) - lower_bound(all(yi), mp(Y[i], 0));

            if (nx > 1 && ny > 1) ans += (ll)xset[i].size() * yset[i].size() - uf.size(i);
        }
    }
    write(ans);
}

Measured memory_bytes: 31360