#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
#define all(x) (x).begin(),(x).end()
#define pushb(x) push_back(x)
#define popb() pop_back()
#define makep make_pair
#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<int(b);++i)
#define rep(...) _overload3(__VA_ARGS__,repi,_rep,)(__VA_ARGS__)
template<typename T> ostream& operator<<(ostream& s, const vector< vector<T> >& vv);
template<typename T> ostream& operator<<(ostream& s, const vector<T>& v);
template<typename T> ll gcd(T a,T b);
template<typename T> ll lcm(T a,T b);
class UnionFind {
public:
    vector <ll> par; // 各元の親を表す配列
    vector <ll> siz; // 素集合のサイズを表す配列(1 で初期化)

    // Constructor
    UnionFind(ll sz_): par(sz_), siz(sz_, 1LL) {
        for (ll i = 0; i < sz_; ++i) par[i] = i; // 初期では親は自分自身
    }
    void init(ll sz_) {
        par.resize(sz_);
        siz.assign(sz_, 1LL);  // resize だとなぜか初期化されなかった
        for (ll i = 0; i < sz_; ++i){
            par[i] = i; // 初期では親は自分自身
        }    
    }

    // Member Function
    // Find
    ll root(ll x) { // 根の検索
        if(par[x]==x){
            return x;
        }else{
            return par[x]=root(par[x]);
        }
        // while (par[x] != x) {
        //     x = par[x] = par[par[x]]; // x の親の親を x の親とする
        // }
        // return x;
    }

    // Union(Unite, Merge)
    bool merge(ll x, ll y) {
        x = root(x);
        y = root(y);
        if (x == y) return false;
        // merge technique（データ構造をマージするテク．小を大にくっつける）
        if (siz[x] < siz[y]) swap(x, y);
        siz[x] += siz[y];
        par[y] = x;
        return true;
    }

    bool find(ll x, ll y) { // 連結判定
        return root(x) == root(y);
    }

    ll size(ll x) { // 素集合のサイズ
        return siz[root(x)];
    }
};

int main(){
    ll n;cin>>n;
    vector<ll> a(n),b(n);
    rep(i,n){
        cin>>a[i]>>b[i];
    }
    vector<int> ord(n);
    iota(all(ord),0);
    sort(all(ord),[&](int x,int y){
        if(a[x]==a[y])return b[x]<b[y];
        return a[x]<a[y];
    });
    UnionFind u(pow(10,5)+1);
    u.init(pow(10,5)+1);
    ll x2=0;
    ll exy=0;
    for(auto& i:ord){
        if(x2==a[i]){
            u.merge(exy,b[i]);
        }else{
            x2=a[i];
            exy=b[i];
        }
    }
    UnionFind u2(pow(10,5)+1);
    u2.init(pow(10,5)+1);
    ll y2=0;
    ll exx=0;
        sort(all(ord),[&](int x,int y){
        if(b[x]==b[y])return a[x]<a[y];
        return b[x]<b[y];
    });
    for(auto& i:ord){
        if(y2==b[i]){
            u2.merge(exx,a[i]);
        }else{
            y2=b[i];
            exx=a[i];
        }
    }
    ll psum=0;
    vector<int> usedx(pow(10,5)+1,0),usedy(pow(10,5)+1,0);
    for(auto& i:ord){
        // cout<<a[i]<<b[i]<<endl;
        if(usedx[u2.root(a[i])]==0){
            if(usedy[u.root(b[i])]==0){
                ll sizex=u2.size(a[i]);
                ll sizey=u.size(b[i]);
                if(sizex==0)sizex++;
                if(sizey==0)sizey++;
                psum+=sizex*sizey;
                // cout<<psum<<endl;
                usedx[u2.root(a[i])]=1;
                usedy[u.root(b[i])]=1;
            }
        }
    }
    cout<<psum-n<<endl;

    
    return 0;
}
template<typename T> ostream& operator<<(ostream& s, const vector< vector<T> >& vv) {
    int len = vv.size();
    for (int i = 0; i < len; ++i) {
        s << vv[i] << endl;
    }
    return s;
}
template<typename T> ostream& operator<<(ostream& s, const vector<T>& v) {
    int len = v.size();
    for (int i = 0; i < len; ++i) {
        s << v[i]; if (i < len - 1) s << ' ';
    }
    return s;
}
template<typename T> ll gcd(T a,T b){
    if(b==0){
        return a;
    }
    return gcd(b,a%b);
}
template<typename T> ll lcm(T a,T b){
    return a*b/gcd(a,b);
}

Measured memory_bytes: 6144