#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <numeric>
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <stack>
#include <math.h>
#include <string.h>
#include <map>
#include <bitset>
#include <set>

#define Z class
#define ln cout<<'\n'
#define ll long long
#define ld long double
#define rep(i, n) for(int i = 0; i < (n); ++i)
//ASCII a=97, A=65
// int H[N]; rep(i,N) scanf("%d", &H[i]);
// int max_x = *std::max_element(x.begin(), x.end());
// int min_y = *std::min_element(y.begin(), y.end());
// sort(vec.begin(), vec.end()); std::greater<>()
// reverse(vec.begin(), vec.end());
// scanf("%s",s);
// printf("%d\n",end-start+1);
// ceil (a + (b - 1)) / b

//===== グラフ受け取り =====//
/*
    int N, M; cin >> N >> M;
    Graph G(N);
    for (int i = 0; i < M; ++i) {
        int a, b;
        cin >> a >> b;
        G[a].push_back(b);

        // 無向グラフの場合は以下を追加
        // G[b].push_back(a);
    }
    
    // == 重み付きグラフ == //
    
    struct Edge {
        int to; 
        int weight; 
        Edge(int t, int w) : to(t), weight(w) { }
    };
    using Graph = vector<vector<Edge>>;
    int main() {
        int N, M; cin >> N >> M;
        Graph G(N);
        for (int i = 0; i < M; ++i) {
            int from, to, weight;
            cin >> from >> to >> weight;
            G[from].push_back(Edge(to, weight));
        }
    }
*/    

using namespace std;
// using Edge = pair<int, int>;
// using Graph = vector<vector<Edge>>;
// using pll = pair<long long, long long>;

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<Z A>void pr(A a){cout<<a;ln;}
template<Z A,Z B>void pr(A a,B b){cout<<a<<' ';pr(b);}
template<Z A,Z B,Z C>void pr(A a,B b,C c){cout<<a<<' ';pr(b,c);}
template<Z A,Z B,Z C,Z D>void pr(A a,B b,C c,D d){cout<<a<<' ';pr(b,c,d);}
template<Z A>void PR(A a,ll n){rep(i,n){if(i)cout<<' ';cout<<a[i];}ln;}

ll GCD(ll a, ll b) { return b ? GCD(b, a%b) : a; }
const long long INF = 1LL << 60;
const int MOD = 1000000007;
bool sortbysec(const pair<int,int> &a, const pair<int,int> &b) { 
    return (a.second < b.second); 
} 

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);
        par[x] += par[y];
        par[y] = x;
        return true;
    }

    int size(int x) {
        return -par[root(x)];
    }
};

const ll MAX = 110000;
ll N;
vector<ll> x, y;

ll solve() {
    UnionFind uf(MAX * 2);
    x.resize(N); y.resize(N);
    rep (i, N) cin >> x[i] >> y[i], uf.merge(x[i], y[i] + MAX);
    
    map<int, ll> mx, my;
    for (int i = 0; i < MAX; i++) mx[uf.root(i)]++;
    for (int i = MAX; i < MAX*2; i++) my[uf.root(i)]++;
    ll res = 0;
    for (int r = 0; r < MAX*2; r++) res += mx[r] * my[r];
    return res - N;
}

int main() {
    cin >> N; cout << solve() << endl;
}






Measured memory_bytes: 30208