#include <bits/stdc++.h>
// using namespace std;
using i64 = int_fast64_t;
#define repeat(i, a, b) for(int i = (a); (i) < (b); ++(i))
#define rep(i, n) repeat(i, 0, n)

namespace edamat {
template <class Info>
struct edge {
    int from, to;
    Info info;
    edge(int to, Info info) : to(to), info(info) {}
    edge(int from, int to, Info info) : from(from), to(to), info(info) {}
};

struct normal_edge {
    normal_edge() {}
};

//! graphは基底クラス！使うときはundirected_graphかdirected_graphを使う。
template <class Info>
class graph {
   protected:
    using edge_type = edge<Info>;
    ::std::size_t N;
    ::std::vector<::std::vector<edge_type>> edges;

   public:
    graph(::std::size_t N_) : N(N_), edges(N_) {}
    virtual void add_edge(int x, int y, Info info = static_cast<Info>(1)) = 0;
    const ::std::vector<edge_type> &delta(int v) const {
        assert(v < N);
        return edges[v];
    }
    const ::std::vector<edge_type> &operator[](int v) const {
        return this->delta(v);
    }
    ::std::size_t size() const { return N; }
};

template <class Info>
class undirected_graph : public graph<Info> {
   public:
    undirected_graph(::std::size_t N) : graph<Info>(N) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        this->edges[x].push_back(edge<Info>(x, y, info));
        this->edges[y].push_back(edge<Info>(y, x, info));
    }
};

template <class Info>
class directed_graph : public graph<Info> {
   public:
    directed_graph(::std::size_t N) : graph<Info>(N) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        this->edges[x].push_back(edge<Info>(x, y, info));
    }
};

/* UnionFind */
template <typename T>
struct UnionFind {
    ::std::vector<T> data;
    ::std::stack<::std::pair<int, T>> history;

    UnionFind() = default;
    UnionFind(int sz) { data.assign(sz, -1); }

    bool unite(int x, int y) {
        x = root(x), y = root(y);
        history.emplace(x, data[x]);
        history.emplace(y, data[y]);
        if(x == y) return (false);
        if(data[x] > data[y]) ::std::swap(x, y);
        data[x] += data[y];
        data[y] = x;
        return (true);
    }

    int root(int k) {
        if(data[k] < 0) return (k);
        return (root(data[k]));
    }

    bool same(int x, int y) { return root(x) == root(y); }

    int size(int k) { return (-data[root(k)]); }

    void undo() {
        data[history.top().first] = history.top().second;
        history.pop();
        data[history.top().first] = history.top().second;
        history.pop();
    }

    void snapshot() {
        while(history.size()) history.pop();
    }

    void rollback() {
        while(history.size()) undo();
    }
};

template <class Info>
struct bipartite_graph : public undirected_graph<Info>, public UnionFind<Info> {
    ::std::size_t A;
    ::std::size_t B;

    bipartite_graph(::std::size_t A_, ::std::size_t B_)
        : A(A_),
          B(B_),
          undirected_graph<Info>(A_ + B_),
          UnionFind<Info>(A_ + B_) {}
    void add_edge(int x, int y, Info info = static_cast<Info>(1)) {
        undirected_graph<Info>::add_edge(x, y + A, info);
        UnionFind<Info>::unite(x, y + A);
    }
    inline int size(int x, bool is_first = true) {
        return (is_first ? UnionFind<Info>::size(x)
                         : UnionFind<Info>::size(x + A));
    };
    // 一つ目の集合のxと二つ目の集合のyが同じ連結成分に属するか
    inline bool same(int x, int y) { return UnionFind<Info>::same(x, y + A); };
    inline int root(int x, bool is_first = true) {
        if(is_first) assert(x < A);
        return (is_first ? UnionFind<Info>::root(x)
                         : UnionFind<Info>::root(x + A));
    }
};
}  // namespace edamat

// 二部グラフ
void ABC131_F() {
    i64 N;
    scanf("%ld", &N);
    edamat::bipartite_graph<i64> g(110000, 110000);
    rep(i, N) {
        i64 a, b;
        scanf("%ld%ld", &a, &b);
        g.add_edge(a, b);
    }
    std::vector<i64> mx(2 * 110000, 0), my(2 * 110000, 0);
    for(int i = 0; i < 110000; i++) {
        mx[g.root(i, true)]++;
        my[g.root(i, false)]++;
    }
    i64 ret = 0;
    rep(i, 2 * 110000) { ret += mx[i] * my[i]; }
    printf("%ld\n", ret - N);
}

// https://atcoder.jp/contests/cf16-final/tasks/codefestival_2016_final_c
void CF2016FC() {
    int N, M;
    scanf("%d%d", &N, &M);
    edamat::bipartite_graph<int> g(N, M);
    for(int i = 0; i < N; i++) {
        int K;
        scanf("%d", &K);
        while(K--) {
            int L;
            scanf("%d", &L);
            g.add_edge(i, L - 1);
        }
    }
    std::vector<int> v(N, 0);
    for(int i = 0; i < N; i++) {
        v[g.root(i)]++;
    }
    for(int i = 0; i < N; i++) {
        if(v[i] == N) {
            printf("YES\n");
            return;
        }
    }
    printf("NO\n");
}

int main() {
    ABC131_F();
    // CF2016FC();
    return 0;
}

Measured memory_bytes: 19200