Problem:
Score : 600 points

Problem Statement

There are N dots in a two-dimensional plane. The coordinates of the i-th dot are (x_i, y_i).

We will repeat the following operation as long as possible:

Choose four integers a, b, c, d (a \neq c, b \neq d) such that there are dots at exactly three of the positions (a, b), (a, d), (c, b) and (c, d), and add a dot at the remaining position.

We can prove that we can only do this operation a finite number of times. Find the maximum number of times we can do the operation.

Constraints

1 \leq N \leq 10^5

1 \leq x_i, y_i \leq 10^5

If i \neq j, x_i \neq x_j or y_i \neq y_j.

All values in input are integers.

Input

Input is given from Standard Input in the following format:

N
x_1 y_1
:
x_N y_N

Output

Print the maximum number of times we can do the operation.

Sample Input 1

3
1 1
5 1
5 5

Sample Output 1

1

By choosing a = 1, b = 1, c = 5, d = 5, we can add a dot at (1, 5). We cannot do the operation any more, so the maximum number of operations is 1.

Sample Input 2

2
10 10
20 20

Sample Output 2

0

There are only two dots, so we cannot do the operation at all.

Sample Input 3

9
1 1
2 1
3 1
4 1
5 1
1 2
1 3
1 4
1 5

Sample Output 3

16

We can do the operation for all choices of the form a = 1, b = 1, c = i, d = j (2 \leq i,j \leq 5), and no more. Thus, the maximum number of operations is 16.

Solution in C++14 (GCC 5.4.1) (judge verdict: Wrong Answer)

#include <bits/stdc++.h>
using namespace std;

#define all(a) (a).begin(), (a).end()
#define rall(a) (a).rbegin(), (a).rend()
#define MIN(a) *min_element(all(a))
#define MAX(a) *max_element(all(a))
#define SUM(a) accumulate(all(a), 0LL)
#define REP(i, n) for(int (i)=0; (i)<(n); (i)++)
#define RREP(i, n) for(int (i)=(n)-1; (i)>=0; (i)--)
#define FOR(i, m, n) for(int (i)=(m); (i)<(n); i++)
#define FORR(i, m, n) for(int (i)=(n)-1; (i)>=(m); i--)
#define debug(x) cerr << #x << " = " << x << endl
//#define int long long

typedef long long ll;
//ll const MOD = 998244353;
ll const MOD = 1e9+7;
int const inf = 1e9;
ll const INF = 1e18;

inline void ios_(){cin.tie(0); ios::sync_with_stdio(false);}
template<typename T> int size(const T& a){return (int)a.size();}
template<typename T> T Div(T a, T b){return (a + b - 1) / b;}
template<typename T> bool chmin(T& a, const T& b){if(a > b){a = b; return true;} return false;}
template<typename T> bool chmax(T& a, const T& b){if(a < b){a = b; return true;} return false;}

//Union-Find Tree
class UnionFind{
public:
    int N;              //頂点数
    int groupnum;       //グループの総数
    vector<int> par;    //par[i]:iの親の番号
    vector<int> size;   //size[i]:iが属するグループと同じグループに属する頂点数
    vector<int> rank;   //ランク

    UnionFind(int n) : N(n), groupnum(n), par(n), size(n, 1), rank(n, 0){  //初期化時はすべて根とする
        for(int i=0; i<n; i++){
            par[i] = i;
        }
    }

    int root(int x){    //データxが属する木の根を再帰で得る
        if(par[x] == x){
            return x;
        }else{
            return par[x] = root(par[x]); //経路圧縮を同時に実行
        }
    }

    void unite(int x, int y){   //xの属する木とyの属する木を併合
        int rx = root(x), ry = root(y);
        if(rx == ry) return;    //xとyの属する木が同じなら何もしない

        if(rank[rx] < rank[ry]) swap(rx, ry);   //rank[rx] >= rank[ry]とする

        par[ry] = rx;   //yの木の根をxの根につける

        if(rank[rx] == rank[ry]) rank[rx]++;    //rankを更新
        size[rx] += size[ry];   //sizeを更新

        groupnum--;     //グループの総数は1減る
    }

    int getSize(int x){         //xの属する木のサイズを返す
        return size[x] = size[root(x)];
    }

    bool same(int x, int y){    //x,yが同じ木に属するときtrue, そうでなければfalse
        return root(x) == root(y);
    }
};


//Weighted Union-Find Tree
class WeightedUnionFind : public UnionFind{
public:
    vector<ll> weight;  //差分重み

    WeightedUnionFind(int n) : UnionFind(n){
        weight.resize(n, 0);
    }

    int root(int x){    //差分重みも同時に更新する
        if(par[x] == x){
            return x;
        }else{
            int r = root(par[x]);
            weight[x] += weight[par[x]];    //  累積和
            return par[x] = r;  //経路圧縮
        }
    }

    ll getWeight(int x){    //xのrootからの差分重みを取得
        root(x);
        return weight[x];
    }

    ll diff(int x, int y){  //yとxの差分を取得
        return getWeight(y) - getWeight(x);
    }

    //weight(y) - weight(x) = w となるようにマージ
    bool unite(int x, int y, ll w){
        w -= diff(x, y);    //rootとの重み差分を補正

        int rx = root(x), ry = root(y);
        if(rx == ry) return false;  //xとyがすでにマージされていたらfalseを返す

        if(rank[rx] < rank[ry]){    //rank[rx] >= rank[ry]とする
            swap(rx, ry);
            w *= -1;                //合わせてwも符号反転
        }

        par[ry] = rx;   //yの木の根をxの根につける

        if(rank[rx] == rank[ry]) rank[rx]++;    //rankを更新
        size[rx] += size[ry];   //sizeを更新
        
        groupnum--;     //グループの総数は1減る

        weight[y] = w;  //xとyの差分をweight[y]に記録

        return true;
    }
};


signed main() {
    int n;
    cin >> n;

    vector<tuple<int,int,int>> v(n);
    REP(i, n){
        int x, y;
        cin >> x >> y;
        v[i] = make_tuple(i, x, y);
    }

    UnionFind UF(n);

    sort(all(v), [](const auto& a, const auto& b){
        if(get<1>(a) == get<1>(b)){
            return get<2>(a) < get<2>(b);
        }
        return get<1>(a) < get<1>(b);
    });

    REP(i, n-1){
        if(get<1>(v[i]) == get<1>(v[i+1])){
            UF.unite(get<0>(v[i]), get<0>(v[i+1]));
        }
    }

    sort(all(v), [](const auto& a, const auto& b){
        if(get<2>(a) == get<2>(b)){
            return get<1>(a) < get<1>(b);
        }
        return get<2>(a) < get<2>(b);
    });

    REP(i, n-1){
        if(get<2>(v[i]) == get<2>(v[i+1])){
            UF.unite(get<0>(v[i]), get<0>(v[i+1]));
        }
    }

    vector<set<int>> stx(n), sty(n);

    REP(i, n){
        int x = get<1>(v[i]), y = get<2>(v[i]);
        stx[UF.root(i)].insert(x);
        sty[UF.root(i)].insert(y);
    }

    ll ans = -n;

    REP(i, n){
        ans += (ll) stx[i].size() * sty[i].size(); 
    }

    cout << ans << endl;

    return 0;
}