#include <iostream>
#include <sstream>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <cstring>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <deque>
#include <map>
#include <set>
#include <bitset>
#include <numeric>
#include <utility>
#include <iomanip>
#include <algorithm>
#include <functional>
#include <unordered_map>
using namespace std;

template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

template<class T> vector<T> make_vec(size_t a) { return vector<T>(a); }
template<class T, class... Ts> auto make_vec(size_t a, Ts... ts) {
  return vector<decltype(make_vec<T>(ts...))>(a, make_vec<T>(ts...));
}
template<class T, class V>
typename enable_if<is_class<T>::value == 0>::type fill(T &t, const V &v) {
    t = v;
}
template<class T, class V>
typename enable_if<is_class<T>::value != 0>::type fill(T &t, const V &v){
    for (auto &e : t) fill(e, v);
}

#define COUT(x) cout << #x << " = " << (x) << " (L" << __LINE__ << ")" << endl
template<class T1, class T2> ostream& operator << (ostream &s, pair<T1,T2> P)
{ return s << '<' << P.first << ", " << P.second << '>'; }
template<class T> ostream& operator << (ostream &s, vector<T> P)
{ for (int i = 0; i < P.size(); ++i) { if (i > 0) { s << " "; } s << P[i]; } return s; }
template<class T> ostream& operator << (ostream &s, vector<vector<T> > P)
{ for (int i = 0; i < P.size(); ++i) { s << endl << P[i]; } return s << endl; }

#define EACH(i, s) for (__typeof__((s).begin()) i = (s).begin(); i != (s).end(); ++i)
template<class T> ostream& operator << (ostream &s, set<T> P)
{ EACH(it, P) { s << "<" << *it << "> "; } return s << endl; }
template<class T1, class T2> ostream& operator << (ostream &s, map<T1,T2> P)
{ EACH(it, P) { s << "<" << it->first << "->" << it->second << "> "; } return s << endl; }





struct UnionFind {
    vector<int> par;
    
    UnionFind(int n) : par(n, -1) { }
    void init(int n) { par.assign(n, -1); }
    
    int root(int x) {
        if (par[x] < 0) return x;
        else return par[x] = root(par[x]);
    }
    
    bool issame(int x, int y) {
        return root(x) == root(y);
    }
    
    bool merge(int x, int y) {
        x = root(x); y = root(y);
        if (x == y) return false;
        if (par[x] > par[y]) swap(x, y); // merge technique
        par[x] += par[y];
        par[y] = x;
        return true;
    }
    
    int size(int x) {
        return -par[root(x)];
    }
};

long long N;
vector<long long> x, y, ax, ay;

long long solve() {
    x.resize(N); y.resize(N); ax.clear(); ay.clear();
    for (int i = 0; i < N; ++i) {
        cin >> x[i] >> y[i];
        ax.push_back(x[i]);
        ay.push_back(y[i]);
    }
    sort(ax.begin(), ax.end());
    ax.erase(unique(ax.begin(), ax.end()), ax.end());
    sort(ay.begin(), ay.end());
    ay.erase(unique(ay.begin(), ay.end()), ay.end());

    int nx = ax.size(), ny = ay.size();
    UnionFind uf(nx + ny);
    for (int i = 0; i < N; ++i) {
        int ix = lower_bound(ax.begin(), ax.end(), x[i]) - ax.begin();
        int iy = lower_bound(ay.begin(), ay.end(), y[i]) - ay.begin();
        uf.merge(ix, iy+nx);
    }

    map<int, long long> mx, my;
    for (int i = 0; i < nx; ++i) {
        int r = uf.root(i);
        mx[r]++;
    }
    for (int i = nx; i < nx + ny; ++i) {
        int r = uf.root(i);
        my[r]++;
    }

    //COUT(mx);
    //COUT(my);

    long long res = 0;
    for (int r = 0; r < nx + ny; ++r) {
        res += mx[r] * my[r];
    }
    return res - N;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    
    while (cin >> N) {
        cout << solve() << endl;
    }
}


















Measured memory_bytes: 16236