#include <iostream>
#include <cstdio>
#include <string>
#include <cstring>
#include <deque>
#include <list>
#include <queue>
#include <stack>
#include <vector>
#include <utility>
#include <algorithm>
#include <map>
#include <set>
#include <complex>
#include <cmath>
#include <limits>
#include <climits>
#include <ctime>
#include <cassert>
#include <numeric>
#include <functional>
#include <bitset>

using namespace std;
using lint = long long int;
using ll = long long int;
using graph = vector<vector<int>>;
using wgraph = vector<vector<pair<int, lint>>>;
const lint INF = 1001001001001001LL;
const lint MOD = 1000000007LL;
int di[] = {-1, 0, 1, 0, -1, 1, 1, -1};
int dj[] = {0, 1, 0, -1, 1, 1, -1, -1};

void yes(){ cout << "yes" << endl; }
void Yes(){ cout << "Yes" << endl; }
void YES(){ cout << "YES" << endl; }
void no(){ cout << "no" << endl; }
void No(){ cout << "No" << endl; }
void NO(){ cout << "NO" << endl; }
void possible(){ cout << "possible" << endl; }
void Possible(){ cout << "Possible" << endl; }
void POSSIBLE(){ cout << "POSSIBLE" << endl; }
void impossible(){ cout << "impossible" << endl; }
void Impossible(){ cout << "Impossible" << endl; }
void IMPOSSIBLE(){ cout << "IMPOSSIBLE" << endl; }

#define rep(i,n) for(int i = 0; i < (n); i++)
#define rrep(i,n) for(int i = 1; i <= (n); i++)
#define drep(i,n) for(int i = (n)-1; i >= 0; i--)
#define srep(i,s,t) for(int i = s; i < t; i++)
#define all(a) a.begin(),a.end()
#define rall(a) a.rbegin(),a.rend()


struct UnionFind{
    
    vector<int> M_par;
    vector<int> M_rank;
    vector<int> M_size;
    vector<int> M_size_x;
    vector<int> M_size_y;

private:
    void init(size_t n = 200010){
      
        M_par.resize(n);
        M_rank.resize(n);
        M_size.resize(n);
        M_size_x.resize(n);
        M_size_y.resize(n);
        for(int i = 0; i < n; i++){
            M_par[i] = i;
            M_rank[i] = 0;
            M_size[i] = 1;
          
            if(i <= 100000){
                M_size_x[i] = 1;
                M_size_y[i] = 0;
            }else{
                M_size_x[i] = 0;
                M_size_y[i] = 1;
            }
        }
    }

public:

    UnionFind(size_t n = 200010){
        init(n);
    }

    size_t find(size_t x){
        if(M_par[x] == x) return x;
        size_t r = find(M_par[x]);
        return M_par[x] = r;
    }
    
    bool same(size_t x, size_t y){
        return find(x) == find(y);
    }

    bool unite(size_t x, size_t y){
        x = find(x);
        y = find(y);
        if(x == y) return false;
        if(M_rank[x] < M_rank[y]) swap(x, y);
        if(M_rank[x] == M_rank[y]) M_rank[x]++;
        M_par[y] = x;
        M_size[x] = M_size[x] + M_size[y];
        M_size_x[x] = M_size_x[x] + M_size_x[y];
        M_size_y[x] = M_size_y[x] + M_size_y[y];
        return true;
    }

    size_t size(size_t x){
        return M_size[find(x)];
    }

    size_t size_x(size_t x){
        return M_size_x[find(x)];
    }

    size_t size_y(size_t x){
        return M_size_y[find(x)];
    }

};

int main(){
  
  UnionFind uf;
  lint n; cin >> n;
  vector<int> x(n);
  vector<int> y(n);
  rep(i, n){
    int xi, yi; cin >> xi >> yi;
    x[i] = xi;
    y[i] = yi + 100000;
    uf.unite(x[i], y[i]);
  }
  
  lint cnt = 0;
  set<int> st;
  rep(i, n){
    
    if(x[i] == uf.find(x[i])){
      if(st.find(x[i]) != st.end()) continue;
      st.insert(x[i]);
      cerr << x[i] << " " << uf.size_x(x[i]) << " " << uf.size_y(x[i]) << endl;
      cnt += max(1LL, (lint)(uf.size_x(x[i]) * uf.size_y(x[i])));
    }
    if(y[i] == uf.find(y[i])){
      if(st.find(y[i]) != st.end()) continue;
      st.insert(y[i]);
      cerr << y[i] << " " << uf.size_x(y[i]) << " " << uf.size_y(y[i]) << endl;
      cnt += max(1LL, (lint)(uf.size_x(y[i]) * uf.size_y(y[i])));
    }
  }
  cout << cnt - n << endl;

  return 0;
}


Measured memory_bytes: 4992