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-----
3
1 1
5 1
5 5

-----Sample Output-----
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.

Difficulty: interview

Starter code:

Solutions:
from collections import defaultdict
import sys


sys.setrecursionlimit(10 ** 6)


def main():
    N = int(input())
    V = 100005
    to = defaultdict(list)
    for _ in range(N):
        X, Y = map(int, input().split())
        Y += V
        to[X].append(Y)
        to[Y].append(X)
    visited = [0] * (2 * V)
    cnt = [0] * 2  # cnt = [cnt of X, cnt of Y]

    def dfs(v):
        if visited[v] == 1:
            return
        visited[v] = 1
        cnt[v // V] += 1
        for nv in to[v]:
            dfs(nv)

    ans = 0
    for v in range(2 * V):
        if visited[v] == 1:
            continue
        cnt = [0] * 2
        dfs(v)
        ans += cnt[0] * cnt[1]
    ans -= N

    print(ans)


def __starting_point():
    main()
__starting_point()

import sys
from collections import defaultdict

sys.setrecursionlimit(10 ** 7)
input = sys.stdin.readline
f_inf = float('inf')
mod = 10 ** 9 + 7


class UnionFind:
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        """
        親が同じか判別する
        """
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        """
        yをxの根に繋ぐ
        """
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if self.parents[x] > self.parents[y]:
            x, y = y, x
        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def same(self, x, y):
        """
        xとyが同じ連結成分か判別する
        """
        return self.find(x) == self.find(y)

    def size(self, x):
        """
        xの連結成分の大きさを返す
        """
        return -self.parents[self.find(x)]

    def kruskal(self, edge):
        """
        :param edge: edge = [(コスト, 頂点1, 頂点2),...]の形で重み付き隣接リストを渡して下さい
        :return: 最小全域木のコストの和
        """
        edge.sort()
        cost_sum = 0
        for cost, node1, node2 in edge:
            if not self.same(node1, node2):
                cost_sum += cost
                self.union(node1, node2)
        return cost_sum


def resolve():
    n = int(input())
    MAX = 10 ** 5 + 10
    uf = UnionFind(2 * MAX)
    XY = []
    for _ in range(n):
        x, y = [int(x) - 1 for x in input().split()]
        y += MAX
        uf.union(x, y)
        XY.append([x, y])

    mx = defaultdict(int)
    my = defaultdict(int)
    for i in range(MAX):
        mx[uf.find(i)] += 1

    for i in range(MAX, 2 * MAX):
        my[uf.find(i)] += 1

    res = 0
    for i in range(2 * MAX):
        res += mx[i] * my[i]

    print((res - n))


def __starting_point():
    resolve()

__starting_point()

from collections import defaultdict


class UnionFind():
    n = 1
    par = [0]
    rnk = [0]

    def __init__(self, size):
        self.n = size
        self.par = [i for i in range(self.n)]
        self.rnk = [0 for i in range(self.n)]

    def find(self, vertex1):
        if self.par[vertex1] == vertex1:
            return vertex1
        else:
            self.par[vertex1] = self.find(self.par[vertex1])
            return self.par[vertex1]

    def unite(self, vertex1, vertex2):
        vertex1 = self.find(vertex1)
        vertex2 = self.find(vertex2)
        if vertex1 == vertex2:
            return
        if (self.rnk[vertex1] < self.rnk[vertex2]):
            self.par[vertex1] = vertex2
        else:
            self.par[vertex2] = vertex1
            if (self.rnk[vertex1] == self.rnk[vertex2]):
                self.rnk[vertex1] += 1

    def same(self, vetrex1, vertex2):
        return self.find(vetrex1) == self.find(vertex2)


N = int(input())
P = [(i,) + tuple(map(int, input().split())) for i in range(N)]
G = UnionFind(N)
P.sort(key=lambda x: x[1])
for i in range(N - 1):
    if P[i][1] == P[i + 1][1]:
        G.unite(P[i][0], P[i + 1][0])
P.sort(key=lambda x: x[2])
for i in range(N - 1):
    if P[i][2] == P[i + 1][2]:
        G.unite(P[i][0], P[i + 1][0])
P.sort(key=lambda x: x[0])
Dx = defaultdict(set)
Dy = defaultdict(set)
for i in range(N):
    j = G.find(i)
    Dx[j].add(P[i][1])
    Dy[j].add(P[i][2])
ans = -N
for j in Dx:
    ans += len(Dx[j]) * len(Dy[j])
print(ans)

n = int(input())
p = [list(map(int, input().split())) for _ in range(n)]
adj = [[] for _ in range(200000)]
for x, y in p:
	adj[x-1].append(y+99999)
	adj[y+99999].append(x-1)

visited = [False for _ in range(200000)]

def dfs(orig):
	stack = [orig]
	edges = len(adj[orig])
	if orig < 100000:
		x_num, y_num = 1, 0
	else:
		x_num, y_num = 0, 1

	while stack:
		v = stack.pop()
		for w in adj[v]:
			if visited[w] == False:
				visited[w] = True
				if w < 100000:
					x_num += 1
				else:
					y_num += 1
				edges += len(adj[w])
				stack.append(w)
	return x_num * y_num - (edges // 2)

ans = 0
for i in range(200000):
	if visited[i] == False and adj[i]:
		visited[i] = True
		ans += dfs(i)
print(ans)

class UnionFind:
    def __init__(self, size):
        self.parent = list(range(size))
        self.height = [0] * size
        self.size = [1] * size
        self.componentCount = size

    def root(self, index):
        if self.parent[index] == index:  # 根の場合
            return index
        rootIndex = self.root(self.parent[index])  # 葉の場合親の根を取得
        self.parent[index] = rootIndex  # 親の付け直し
        return rootIndex

    def union(self, index1, index2):  # 結合
        root1 = self.root(index1)
        root2 = self.root(index2)

        if root1 == root2:  # 連結されている場合
            return

        self.componentCount -= 1  # 連結成分を減らす

        if self.height[root1] < self.height[root2]:
            self.parent[root1] = root2  # root2に結合
            self.size[root2] += self.size[root1]
        else:
            self.parent[root2] = root1  # root1に結合
            self.size[root1] += self.size[root2]
            if self.height[root1] == self.height[root2]:
                self.height[root1] += 1
        return

    def isSameRoot(self, index1, index2):
        return self.root(index1) == self.root(index2)

    def sizeOfSameRoot(self, index):
        return self.size[self.root(index)]

from collections import defaultdict

N = int(input())
XY = [tuple(map(int, input().split())) for _ in range(N)]

xGrp = defaultdict(list)
yGrp = defaultdict(list)
for i, (x, y) in enumerate(XY):
    xGrp[x].append(i)
    yGrp[y].append(i)

tree = UnionFind(N)
for grp in list(xGrp.values()):
    for l, r in zip(grp, grp[1:]):
        tree.union(l, r)
for grp in list(yGrp.values()):
    for l, r in zip(grp, grp[1:]):
        tree.union(l, r)

xGrpList = defaultdict(set)
yGrpList = defaultdict(set)
for i, (x, y) in enumerate(XY):
    xGrpList[tree.root(i)].add(x)
    yGrpList[tree.root(i)].add(y)

ans = 0
for i in range(N):
    if i != tree.root(i):
        continue
    X, Y = xGrpList[i], yGrpList[i]
    if len(X) + len(Y) < 3:
        continue
    A = len(X) * len(Y)
    ans += A - tree.sizeOfSameRoot(i)
print(ans)


class UnionFind:
    def __init__(self, N):
        self.p = list(range(N))
        self.rank = [0] * N
        self.size = [1] * N

    def find_root(self, x):
        if self.p[x] != x:
            self.p[x] = self.find_root(self.p[x])

        return self.p[x]

    def same(self, x, y):
        return self.find_root(x) == self.find_root(y)

    def unite(self, x, y):
        u = self.find_root(x)
        v = self.find_root(y)

        if u == v: return

        if self.rank[u] < self.rank[v]:
            self.p[u] = v
            self.size[v] += self.size[u]
            self.size[u] = 0
        else:
            self.p[v] = u
            self.size[u] += self.size[v]
            self.size[v] = 0

            if self.rank[u] == self.rank[v]:
                self.rank[u] += 1

    def get_size(self, x):
        return self.size[self.find_root(x)]

n = int(input())
xy = [tuple(int(x) for x in input().split()) for _ in range(n)]

sx = set()
sy = set()

for x, y in xy:
    sx.add(x)
    sy.add(y)

dx = dict()
dy = dict()

for i, x in enumerate(sx):
    dx[x] = i

len_x = len(sx)
m = len(sx) + len(sy)

for i, y in enumerate(sy):
    dy[y] = i + len_x


uft = UnionFind(m)

for x, y in xy:
    uft.unite(dx[x], dy[y])

mx = [0] * m
my = [0] * m
for i in range(len_x):
    mx[uft.find_root(i)] += 1
for i in range(len_x, m):
    my[uft.find_root(i)] += 1

ans = 0
for i in range(m):
    ans += mx[i] * my[i]

print(ans - n)

#!/usr/bin/env python3
import sys
from collections.abc import Iterable
from math import *
from itertools import *
from collections import *
from functools import *
from operator import *
try:
    from math import gcd
except Exception:
    from fractions import gcd


class DGraph:
    def __init__(self, n, edges):
        self._vertice = [{} for _ in range(n)]
        for e in edges:
            self._vertice[e[0]][e[1]] = e[2] if len(e) == 3 else 1

    def __len__(self):
        return len(self._vertice)

    def edges(self, n=None):
        srcs = list(range(len(self))) if n is None else [n]
        return sum([[(s, d, c) for d, c in list(self._vertice[s].items())] for s in srcs], [])

    def transpose(self):
        tedges = [(t, f, c) for f, t, c in self.edges()]
        return DGraph(len(self), tedges)

    def cost(self, src, dst):
        return self._vertice[src].get(dst, float('inf'))

class Graph(DGraph):
    def __init__(self, n, edges):
        nd_edges = edges + [(d, s, c) for s, d, c in edges]
        super().__init__(n, nd_edges)

    def transpose(self):
        return self


def solve(N: int, x: "List[int]", y: "List[int]"):
    xs = {v:i for i, v in enumerate(set(x))}
    ys = {v:i for i, v in enumerate(set(y), len(xs))}
    
    n = len(xs) + len(ys)
    es = [(xs[cx], ys[cy], 1) for cx, cy in zip(x, y)]
    g = Graph(n, es)

    visited = [False] * len(g)
    def dfs(g, src):
        nonlocal visited
        stack = [(src, 0)]
        while stack:
            n, c = stack.pop()
            if visited[n]:
                continue
            visited[n] = True

            for _, d, _ in g.edges(n):
                #yield d
                stack.append((d, c + 1))
            yield n

    def _f(i):
        ls = list(i)
        if len(ls) == 0:
            return 0

        xc = 0
        yc = 0
        es = 0
        for i in ls:
            es += len(g.edges(i))
            if i < len(xs):
                xc += 1
            else:
                yc += 1
        es //= 2

        if xc < 2 or yc < 2:
            return 0

        return xc * yc - es

    return sum([_f(dfs(g, i)) for i in range(n)])


def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))  # type: int
    x = [int()] * (N)  # type: "List[int]" 
    y = [int()] * (N)  # type: "List[int]" 
    for i in range(N):
        x[i] = int(next(tokens))
        y[i] = int(next(tokens))
    result = solve(N, x, y)
    if isinstance(result, Iterable) and not isinstance(result, str):
        result = '\n'.join([str(v) for v in result])
    print(result)

def __starting_point():
    main()

__starting_point()

N = int(input())
xy = [tuple(map(int,input().split())) for _ in range(N)]
shift = 10**5+1

nodes = {}
visited = {}

for x,y in xy:
    y += shift
    if x in nodes:
        nodes[x].append(y)
    else:
        nodes[x] = [y]
    if y in nodes:
        nodes[y].append(x)
    else:
        nodes[y] = [x]
    visited[x] = False
    visited[y] = False

ans = 0

def dfs(s, nodes,visited):
    q = [s]
    cnt_x = 0
    cnt_y = 0
    
    while q:
        v = q.pop()
        if visited[v]:
            continue
        visited[v] = True
        
        if v//shift < 1:
            cnt_x += 1
        else:
            cnt_y += 1
            
        q.extend(nodes[v])
        
    return visited, cnt_x*cnt_y

for s in nodes:
    if visited[s]:
        continue
    visited, cnt = dfs(s,nodes, visited)
    ans += cnt
    
print(ans - N)

# -*- coding: utf-8 -*-

import sys
from collections import defaultdict

def input(): return sys.stdin.readline().strip()
def list2d(a, b, c): return [[c] * b for i in range(a)]
def list3d(a, b, c, d): return [[[d] * c for j in range(b)] for i in range(a)]
def list4d(a, b, c, d, e): return [[[[e] * d for j in range(c)] for j in range(b)] for i in range(a)]
def ceil(x, y=1): return int(-(-x // y))
def INT(): return int(input())
def MAP(): return list(map(int, input().split()))
def LIST(N=None): return list(MAP()) if N is None else [INT() for i in range(N)]
def Yes(): print('Yes')
def No(): print('No')
def YES(): print('YES')
def NO(): print('NO')
sys.setrecursionlimit(10 ** 9)
INF = 10 ** 18
MOD = 10 ** 9 + 7

class UnionFind:
    """ Union-Find木 """

    def __init__(self, n):
        self.n = n
        # 親要素のノード番号を格納。par[x] == xの時そのノードは根
        # 1-indexedのままでOK、その場合は[0]は未使用
        self.par = [i for i in range(n+1)]
        # 木の高さを格納する（初期状態では0）
        self.rank = [0] * (n+1)
        # あるノードを根とする集合に属するノード数
        self.size = [1] * (n+1)
        # あるノードを根とする集合が木かどうか
        self.tree = [True] * (n+1)

    def find(self, x):
        """ 根の検索(グループ番号と言えなくもない) """
        # 根ならその番号を返す
        if self.par[x] == x:
            return x
        else:
            # 走査していく過程で親を書き換える
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        """ 併合 """
        # 根を探す
        x = self.find(x)
        y = self.find(y)

        # 木かどうかの判定用
        if x == y:
            self.tree[x] = False
            return
        if not self.tree[x] or not self.tree[y]:
            self.tree[x] = self.tree[y] = False

        # 木の高さを比較し、低いほうから高いほうに辺を張る
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
            self.size[y] += self.size[x]
        else:
            self.par[y] = x
            self.size[x] += self.size[y]
            # 木の高さが同じなら片方を1増やす
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    def is_same(self, x, y):
        """ 同じ集合に属するか判定 """
        return self.find(x) == self.find(y)

    def get_size(self, x=None):
        if x is not None:
            """ あるノードの属する集合のノード数 """
            return self.size[self.find(x)]
        else:
            """ 集合の数 """
            res = set()
            for i in range(self.n+1):
                res.add(self.find(i))
            # グループ0の分を引いて返却
            return len(res) - 1
    
    def is_tree(self, x):
        """ 木かどうかの判定 """
        return self.tree[self.find(x)]

MAX = 10 ** 5
N = INT()
uf = UnionFind(MAX * 2)
edges = []
for i in range(N):
    x, y = MAP()
    edges.append((x, y))
    uf.union(x, y+MAX)

# 連結成分Sが持つ辺の数i,xの頂点数j,yの頂点数k
# D[S] = (i, j, k)
D = defaultdict(lambda: [0, 0, 0])
# 辺の集計
for x, y in edges:
    D[uf.find(x)][0] += 1
# 頂点の集計
for x in range(1, MAX+1):
    y = x + MAX
    D[uf.find(x)][1] += 1
    D[uf.find(y)][2] += 1

ans = 0
# 各連結成分についての、追加できる辺の数を合計する
for edgecnt, xcnt, ycnt in list(D.values()):
    # 追加できる辺の数 = 完全2部グラフの辺数 - 既にある辺の数
    ans += xcnt * ycnt - edgecnt
print(ans)


class UnionFind:
    def __init__(self, n):
        self.n = n
        self.p = [e for e in range(n)]
        self.rank = [0] * n
        self.size = [1] * n

    def same(self, u, v):
        return self.find_set(u) == self.find_set(v)

    def unite(self, u, v):
        u = self.find_set(u)
        v = self.find_set(v)

        if u == v:
            return

        if self.rank[u] > self.rank[v]:
            self.p[v] = u
            self.size[u] += self.size[v]
        else:
            self.p[u] = v
            self.size[v] += self.size[u]
            if self.rank[u] == self.rank[v]:
                self.rank[v] += 1

    def find_set(self, u):
        if u != self.p[u]:
            self.p[u] = self.find_set(self.p[u])

        return self.p[u]

    def update_p(self):
        for u in range(self.n):
            self.find_set(u)

    def get_size(self, u):
        return self.size[self.find_set(u)]


n = int(input())
xy = [list(map(int, input().split())) for _ in range(n)]
MAX = 10 ** 5

uf = UnionFind(MAX * 2)

for x, y in xy:
    x -= 1
    y -= 1
    uf.unite(x, y + MAX)

x_size = [0] * MAX * 2
y_size = [0] * MAX * 2
for i in range(MAX * 2):
    root = uf.find_set(i)
    if i < MAX:
        x_size[root] += 1
    else:
        y_size[root] += 1

ans = -n
for i in range(MAX * 2):
    ans += x_size[i] * y_size[i]

print(ans)


import sys
sys.setrecursionlimit(10**5)

def find_parent(i):
    aaa=0
    while True:
        if parent[i]<0:
            aaa=i
            break
        else:
            i=parent[i]
    return aaa

def connect(A,B):
    A=find_parent(A)
    B=find_parent(B)
    if A!=B:
        p_A,p_B=parent[A],parent[B]
        if p_A<p_B:
            parent[A]+=p_B
            parent[B]=A
            child[A].append(B)
        else:
            parent[B]+=p_A
            parent[A]=B
            child[B].append(A)

n=int(input())
x_max=0
y_max=0
xy=[]
for _ in range(n):
    x,y=map(int,input().split())
    x_max=max(x_max,x)
    y_max=max(y_max,y)
    xy.append([x,y])
data=[[] for _ in range(x_max+1)]
parent=[-1]*(x_max+1)
child=[[] for i in range(x_max+1)]
lst=[0]*(y_max+1)
for x,y in xy:
    data[x].append(y)
    if lst[y]==0:
        lst[y]=x
    else:
        connect(lst[y],x)

lsls=[]
for i in range(1,x_max+1):
    if parent[i]>=0:
        continue
    if not data[i]:
        continue
    que=[i]
    h=[i]
    while que:
        u=que.pop()
        for v in child[u]:
            que.append(v)
            h.append(v)
    lsls.append(h)

ans=0
for u in lsls:
    L=len(u)
    flag=[0]*(y_max+1)
    cnt=0
    count=0
    for v in u:
        for w in data[v]:
            count+=1
            if flag[w]==0:
                flag[w]=1
                cnt+=1
    ans+=L*cnt-count

print(ans)

"""
https://atcoder.jp/contests/abc131/submissions/6086380
"""
import os
import sys

import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import connected_components

if os.getenv("LOCAL"):
    sys.stdin = open("_in.txt", "r")

sys.setrecursionlimit(2147483647)
INF = float("inf")

N = int(sys.stdin.readline())
X, Y = list(zip(*[list(map(int, sys.stdin.readline().split())) for _ in range(N)]))
PAD = 10 ** 5 + 1
X = np.array(X)
Y = np.array(Y) + PAD

graph = csr_matrix(([True] * N, (X, Y)), shape=(PAD * 2, PAD * 2))
# i から j までのパスがあれば components[i] と components[j] が同じ値
_, components = connected_components(graph)

# 同じ連結成分に含まれる X の数
# x_cnt[i]: 連結成分 i に含まれる x の数
x_cnt = np.bincount(components[:PAD], minlength=PAD * 2)
y_cnt = np.bincount(components[PAD:], minlength=PAD * 2)
# 同じ連結成分である x の数と y の数をかけた数だけその間に辺を張れる
# すでにある分は引く
print(((x_cnt * y_cnt).sum() - N))


N = int(input())
XY = [tuple(map(int,input().split())) for i in range(N)]

class UnionFind:
    def __init__(self,N):
        self.parent = [i for i in range(N)]
        self._size = [1] * N
        self.count = 0
    def root(self,a):
        if self.parent[a] == a:
            return a
        else:
            self.parent[a] = self.root(self.parent[a])
            return self.parent[a]
    def is_same(self,a,b):
        return self.root(a) == self.root(b)
    def unite(self,a,b):
        ra = self.root(a)
        rb = self.root(b)
        if ra == rb: return
        if self._size[ra] < self._size[rb]: ra,rb = rb,ra
        self._size[ra] += self._size[rb]
        self.parent[rb] = ra
        self.count += 1
    def size(self,a):
        return self._size[self.root(a)]

M = 10**5
uf = UnionFind(2*M)
for x,y in XY:
    a,b = x-1,y-1+M
    if uf.is_same(a,b): continue
    uf.unite(a,b)
for i in range(2*M):
    uf.root(i)

rt = []
for i in range(2*M):
    rt.append(uf.root(i))
from collections import Counter
xr = Counter(rt[:M])
yr = Counter(rt[M:])

ans = 0
for k,v in xr.items():
    ans += yr[k] * v
ans -= N
print(ans)

import sys

read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines

n = int(readline())
xy = list(map(int, read().split()))
XY = list(zip(xy, xy))

max_num = 10 ** 5
delta = 10 ** 5 + 10

graph = [[] for _ in range(max_num + delta + 1)]

for x, y in XY:
    graph[x].append(delta + y)
    graph[delta + y].append(x)

x_used = [False] * (max_num + 1)
y_used = [False] * (max_num + 1)


def dfs(idx):
    x_res = 0
    y_res = 0
    edgenum = 0
    stack = []
    stack.append(idx)
    if idx >= delta:
        y_used[idx % delta] = True
    else:
        x_used[idx] = True
    while stack:
        v = stack.pop()
        if v >= delta:
            # if y_used[v % delta]:
            #     continue
            # y_used[v % delta] = True
            y_res += 1
        else:
            # if x_used[v]:
            #     continue
            # x_used[v] = True
            x_res += 1
        for u in graph[v]:
            if u >= delta:
                if y_used[u % delta]:
                    continue
                y_used[u % delta] = True
            else:
                if x_used[u]:
                    continue
                x_used[u] = True
            edgenum += 1
            stack.append(u)
    res = x_res * y_res
    return res


ans = 0
for i in range(max_num + 1):
    if x_used[i]:
        continue
    temp = dfs(i)
    ans += temp

for i in range(max_num + 1):
    if y_used[i]:
        continue
    i += delta
    temp = dfs(i)
    ans += temp

print((ans - n))


from collections import defaultdict,deque
import sys,heapq,bisect,math,itertools,string,queue
sys.setrecursionlimit(10**8)
INF = float('inf')
mod = 10**9+7
eps = 10**-7
def inpl(): return list(map(int, input().split()))
def inpl_str(): return list(input().split())


class PotentialUnionFind:
    def __init__(self,N): # 頂点数 N
        self.table = [i for i in range(N)]    # 親 table[x] == x で根
        self.rank  = [1 for i in range(N)]    # 木の長さ
        self.size  = [1 for i in range(N)]    # 集合のサイズ
        self.diffweight = [0 for i in range(N)]

    def Find(self,x):    #xの根を返す
        if self.table[x] == x:
            return x
        else:
            root = self.Find(self.table[x]) #親の更新
            self.size[x] = self.size[self.table[x]]
            self.diffweight[x] += self.diffweight[self.table[x]]
            self.table[x] = root
            return root

    def Unite(self,x,y,w): #xとyをDiff(x,y)=W で繋げる
        w = w - self.Weight(y) + self.Weight(x)
        x,y = self.Find(x), self.Find(y)
        sx,sy = self.Size(x), self.Size(y)
        if x == y: return
        if self.rank[x] > self.rank[y]:
            self.table[y] = x
            self.size[x] = sx + sy
            self.diffweight[y] = w
        else:
            self.table[x] = y
            self.size[y] = sx + sy
            self.diffweight[x] = -w
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1

    def Check(self,x,y):
        return self.Find(x) == self.Find(y)

    def Size(self,x):
        return self.size[self.Find(x)]

    def Weight(self,x): # 重さ(根からの距離)
        self.Find(x)
        return self.diffweight[x]

    def Diff(self,x,y): # 繋がってる二点間距離
        return self.Weight(y) - self.Weight(x)


N = int(input())
PUF = PotentialUnionFind(10**5+1)

xys = [inpl() for _ in range(N)]
xys.sort()
tatedict = defaultdict(list)
xx = set()
for x,y in xys:
    tatedict[x].append(y)
    xx.add(x)

for x in xx:
    s = tatedict[x][0]
    for t in tatedict[x]:
        if not PUF.Check(s,t):
            PUF.Unite(s,t,1)

ans = 0
for x in xx:
    s = tatedict[x][0]
    ans += PUF.Size(s) - len(tatedict[x])

print(ans)


from collections import Counter
import sys
input = sys.stdin.readline

# Union-Findデータ構造
class UnionFind:
    def __init__(self, numV):
        self.pars = list(range(numV))
        self.ranks = [0] * numV
    def find(self, x):
        if self.pars[x] == x: return x
        else:
            self.pars[x] = self.find(self.pars[x])
            return self.pars[x]
    def union(self, x, y):
        x, y = self.find(x), self.find(y)
        if x == y: return
        if self.ranks[x] < self.ranks[y]:
            self.pars[x] = y
        else:
            self.pars[y] = x
            if self.ranks[x] == self.ranks[y]:
                self.ranks[x] += 1
    def same(self, x, y):
        return self.find(x) == self.find(y)

N = int(input())
xys = [tuple(map(int, input().split())) for _ in range(N)]

M = 10**5
UF = UnionFind(2*M)
for x, y in xys:
    x, y = x-1, y-1
    UF.union(x, y+M)

for x in range(2*M):
    UF.find(x)

cntX = Counter(UF.pars[:M])
cntY = Counter(UF.pars[M:])

ans = 0
for v in list(cntX.keys()):
    ans += cntX[v] * cntY[v]

print((ans - N))


class UnionFind:
    def __init__(self, n):
        self.n = n
        self.parent = [None]*n

    def root(self, x):
        if self.parent[x] == None:
            return x
        else:
            return self.root(self.parent[x])

    def union(self, x, y):
        x0 = self.root(x)
        y0 = self.root(y)
        if x0 == y0:
            return
        if x0 > y0:
            x0, y0 = y0, x0
        self.parent[y0] = x0


n = int(input())
m = 10**5
uf = UnionFind(2*m)
X, Y = [], []
for i in range(n):
    x, y = list(map(int, input().split()))
    X.append(x-1)
    Y.append(y-1+m)
    uf.union(x-1, y-1+m)
R = {}
for x, y in zip(X, Y):
    r = uf.root(x)
    try:
        R[r][0].add(x)
        R[r][1].add(y)
    except:
        R[r] = [{x}, {y}]
ans = 0
for r in list(R.keys()):
    ans += len(R[r][0])*len(R[r][1])
print((ans-n))


import sys
input = sys.stdin.readline
M = 10**5

n = int(input())
G = [[] for _ in range(2*M+1)]
for _ in range(n):
  x, y = map(int, input().split())
  G[x].append(y+M)
  G[y+M].append(x)
seen = [False]*(2*M+1)
def dfs(v):
  stack = [v]
  seen[v] = True
  cnt_x, cnt_y = 0, 0
  while stack:
    v = stack.pop()
    if v <= M:
      cnt_x += 1
    else:
      cnt_y += 1
    for nv in G[v]:
      if seen[nv]:
        continue
      seen[nv] = True
      stack.append(nv)
  return cnt_x*cnt_y
ans = 0
for i in range(2*M+1):
  if not seen[i]:
    ans += dfs(i)
ans -= n
print(ans)

import sys
from collections import deque
from operator import itemgetter
input = sys.stdin.readline

def main():
    n = int(input())
    p = [list(map(int, input().split()))+[i] for i in range(n)]
    tree = [[] for _ in range(n)]
    
    p.sort()
    for i in range(n-1):
        if p[i][0] == p[i+1][0]:
            tree[p[i][2]].append(p[i+1][2])
            tree[p[i+1][2]].append(p[i][2])

    p.sort(key=itemgetter(1))
    for i in range(n-1):
        if p[i][1] == p[i+1][1]:
            tree[p[i][2]].append(p[i+1][2])
            tree[p[i+1][2]].append(p[i][2])

    p.sort(key=itemgetter(2))

    
    already = [False]*n
    ans = -n
    for i in range(n):
        if already[i]:
            continue
        already[i] = True
        not_yet = deque([i])
        key_x = {p[i][0]}
        key_y = {p[i][1]}
        while not_yet:
            key = not_yet.popleft()
            for v in tree[key]:
                if already[v]:
                    continue
                already[v] = True
                key_x.add(p[v][0])
                key_y.add(p[v][1])
                not_yet.append(v)
        ans += len(key_x)*len(key_y)
    
    print(ans)
        
    
    
def __starting_point():
    main()


__starting_point()

n = int(input())
MAX = 10**5
adj = [[[] for u in range(MAX+1)] for t in range(2)]
vis = [[False for u in range(MAX+1)] for t in range(2)]
for i in range(n):
	x, y = map(int,input().split())
	adj[0][x].append(y)
	adj[1][y].append(x)

import sys
sys.setrecursionlimit(4*10**5+1)

def dfs(t,u): # Left, Right, Edges
	vis[t][u] = True

	ct = [0,0,0]
	ct[t] += 1
	for v in adj[t][u]:
		ct[2] += 1
		if not vis[t^1][v]:
			ctv = dfs(t^1,v)

			ct[0] += ctv[0]
			ct[1] += ctv[1]
			ct[2] += ctv[2]

	return ct

ans = 0
for u in range(1,MAX+1):
	if not vis[0][u]:
		L, R, M = dfs(0,u)
		ans += L*R-M//2

print(ans)

class UnionFind:
    def __init__(self, n):
        self.ps = [-1] * (n + 1)

    def find(self, x):
        if self.ps[x] < 0:
            return x
        else:
            self.ps[x] = self.find(self.ps[x])
            return self.ps[x]

    def unite(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return False
        if self.ps[x] > self.ps[y]:
            x, y = y, x
        self.ps[x] += self.ps[y]
        self.ps[y] = x
        return True

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def size(self, x):
        x = self.find(x)
        return -self.ps[x]

n = int(input())
v = 10**5 + 5
uf = UnionFind(v*2 + 10)
for _ in range(n):
  x, y = map(int, input().split())
  uf.unite(x, y+v)
d = dict()
for i in range(v*2):
  if uf.size(i) > 1:
    p = uf.find(i)
    if p not in d:
      d[p] = [0, 0]
    d[p][i > v] += 1
print(sum(x*y for x,y in d.values()) - n)

# -*- coding: utf-8 -*-
import bisect
import heapq
import math
import random
import sys
from collections import Counter, defaultdict, deque
from decimal import ROUND_CEILING, ROUND_HALF_UP, Decimal
from functools import lru_cache, reduce
from itertools import combinations, combinations_with_replacement, product, permutations
from operator import add, mul, sub

sys.setrecursionlimit(100000)
input = sys.stdin.readline


def read_int():
    return int(input())


def read_int_n():
    return tuple(map(int, input().split()))


def read_float():
    return float(input())


def read_float_n():
    return list(map(float, input().split()))


def read_str():
    return input().strip()


def read_str_n():
    return list(map(str, input().split()))


def error_print(*args):
    print(*args, file=sys.stderr)


def mt(f):
    import time

    def wrap(*args, **kwargs):
        s = time.time()
        ret = f(*args, **kwargs)
        e = time.time()

        error_print(e - s, 'sec')
        return ret

    return wrap


@mt
def slv(N, XY):
    g = [[] for _ in range((10**5)*2)]
    offset = 10**5
    cs = set()
    for x, y in XY:
        x -= 1
        y = y - 1 + offset
        g[x].append(y)
        g[y].append(x)
        cs.add(x)
        cs.add(y)
    ans = 0
    while cs:
        error_print(len(cs))
        q = set([cs.pop()])
        done = set()
        done_c = set()
        while q:
            v = q.pop()
            for u in g[v]:
                if v >= offset:
                    e = (u, v)
                else:
                    e = (v, u)
                if e not in done:
                    q.add(u)
                    done.add(e)
                    done_c.add(u)
                    done_c.add(v)
        xn = set()
        yn = set()
        for v, u in done:
            for z in (v, u):
                if z >= offset:
                    yn.add(z)
                else:
                    xn.add(z)
        ans += (len(xn) * len(yn)) - len(done)
        cs -= done_c

    return ans

def main():
    N = read_int()
    XY = [read_int_n() for _ in range(N)]
    print(slv(N, XY))


def __starting_point():
    main()

__starting_point()

N = int(input())

X = [[] for _ in range(10**5)]
Y = [[] for _ in range(10**5)]

P = [tuple(map(lambda x: int(x) - 1, input().split())) for _ in range(N)]

for i in range(N):
    x, y = P[i]
    X[x].append(y)
    Y[y].append(x)

x_used = [0 for _ in range(10**5)]
y_used = [0 for _ in range(10**5)]
res = 0

for i in range(10**5):
    if x_used[i]:
        continue
    stack = [(0, i)]
    x_cnt = 1
    x_used[i] = 1
    y_cnt = 0
    edge = 0
    while stack:
        axis, node = stack.pop()
        if axis == 0:
            for adj in X[node]:
                edge += 1
                if y_used[adj]:
                    continue
                y_cnt += 1
                y_used[adj] = 1
                stack.append((1, adj))
        else:
            for adj in Y[node]:
                if x_used[adj]:
                    continue
                x_cnt += 1
                x_used[adj] = 1
                stack.append((0, adj))
    res += x_cnt * y_cnt - edge

print(res)

import sys
input = sys.stdin.readline
max_n = 10**5
ans = 0
n = int(input())

class UnionFind:
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.sizea = [1 if _ < n//2 else 0 for _ in range(n)]
        self.sizeb = [0 if _ < n//2 else 1 for _ in range(n)]
        self.sizec = [1 for _ in range(n)]
        self.rank = [0] * (n)
        
    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def is_root(self, x):
        if self.par[x] == x:
            return True
        else:
            return False
    
    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.sizea[x] += self.sizea[y]
            self.sizeb[x] += self.sizeb[y]
            self.sizec[x] += self.sizec[y]
        else:
            self.sizec[x] += 1          

    def get_size(self, x):
        x = self.find(x)
        return self.sizea[x],self.sizeb[x],self.sizec[x]

uf = UnionFind(2*max_n)

for _ in range(n):
  x,y = map(int, input().split())
  x -= 1
  y += max_n - 1
  uf.union(x, y)

for i in range(max_n):
  if uf.is_root(i):
    a,b,c = uf.get_size(i)
    if a*b > 1:
      ans += a*b -(c-1)
print(ans)

import sys
from collections import Counter

read = sys.stdin.read
readline = sys.stdin.readline
readlines = sys.stdin.readlines
sys.setrecursionlimit(10 ** 9)
INF = 1 << 60
MOD = 1000000007


class UnionFind:
    # Reference: https://note.nkmk.me/python-union-find/
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * n

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def size(self, x):
        return -self.parents[self.find(x)]

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n) if self.find(i) == root]

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def group_count(self):
        return len(self.roots())

    def all_group_members(self):
        return {r: self.members(r) for r in self.roots()}


def main():
    N, *XY = list(map(int, read().split()))

    MAX = 100010
    uf = UnionFind(MAX * 2)
    for x, y in zip(*[iter(XY)] * 2):
        uf.union(x, y + MAX)

    x_comp = Counter()
    y_comp = Counter()
    for i in range(MAX):
        x_comp[uf.find(i)] += 1
    for i in range(MAX, 2 * MAX):
        y_comp[uf.find(i)] += 1

    ans = 0
    for k in list(x_comp.keys()):
        ans += x_comp[k] * y_comp[k]

    ans -= N

    print(ans)
    return


def __starting_point():
    main()

__starting_point()

# https://drken1215.hatenablog.com/entry/2019/06/23/004700
# https://atcoder.jp/contests/abc131/submissions/7975278
# https://tjkendev.github.io/procon-library/python/union_find/union_find.html

def main():
    from collections import Counter
    import sys
    input = sys.stdin.readline

    MX = 10 ** 5 + 10

    *p, = [r for r in range(MX * 2)]

    def root(x):
        if x == p[x]:
            return x
        p[x] = y = root(p[x])
        return y

    def unite(x, y):
        px = root(x)
        py = root(y)
        if px == py:
            return 0
        if px < py:
            p[py] = px
        else:
            p[px] = py
        return 1

    n = int(input())
    for _ in range(n):
        x, y = (int(x) - 1 for x in input().split())  # [1,)->[0,)
        unite(x, y + MX)

    ctr_x = Counter(root(r) for r in range(MX))
    ctr_y = Counter(root(r) for r in range(MX, MX * 2))

    rs = set(ctr_x.keys())
    rs.update(list(ctr_y.keys()))

    ans = sum(ctr_x[r] * ctr_y[r] for r in rs) - n

    print(ans)


def __starting_point():
    main()

__starting_point()

import sys
sys.setrecursionlimit(10**6)

MAXVAL = 10**5
N = int(input())
edge = [[] for _ in range(MAXVAL*2)]
visited = [0] * (MAXVAL*2)
ans = 0
xnum = 0; ynum = 0; nodes = 0

def dfs(n):
    nonlocal xnum, ynum, nodes
    if visited[n] == 1:
        return True
    if n < MAXVAL:
        xnum += 1
    else:
        ynum += 1
    visited[n] = 1
    for item in edge[n]:
        nodes += 1
        dfs(item)
    return True

for i in range(N):
    x, y = [int(item) for item in input().split()]
    x -= 1; y -= 1
    edge[x].append(y+MAXVAL)
    edge[y+MAXVAL].append(x)

for i in range(MAXVAL*2):
    if visited[i] == 1:
        continue
    xnum = 0; ynum = 0; nodes = 0
    dfs(i)
    if nodes >= 3:
        ans += xnum * ynum - nodes // 2
print(ans)

import sys
sys.setrecursionlimit(10**6)

n = int(input())
edges = [[] for i in range(2*10**5)]
for i in range(n):
    xi, yi = map(int, input().split())
    xi -= 1
    yi = yi - 1 + 10**5
    edges[xi].append(yi)
    edges[yi].append(xi)

already = set([])
def dfs(x, y, node):
    if node in already:
        return x, y
    if node < 10**5:
        x += 1
    else:
        y += 1
    already.add(node)
    for next_node in edges[node]:
        x, y = dfs(x, y, next_node)
    return x, y

num = 0
for i in range(10**5):
    x, y = dfs(0, 0, i)
    num += x * y
print(num-n)

import sys
input = sys.stdin.readline
sys.setrecursionlimit(10**7)

n = int(input())
xy = [[[] for _ in range(10**5+1)] for __ in range(2)]

for _ in range(n):
    x,y = map(int,input().split())
    xy[0][x].append(y)
    xy[1][y].append(x)

ans = 0
used = [[False for _ in range(10**5+1)] for __ in range(2)]

def dfs(i,flag):
    for v in xy[flag][i]:
        if not used[1-flag][v]:
            used[1-flag][v] = True
            dfs(v,1-flag)
            count[1-flag] += 1

for i in range(10**5+1):
    if xy[0][i] and not used[0][i]:
        used[0][i] = True
        count = [1,0]
        dfs(i,0)
        ans += count[0]*count[1]

ans -= n
print(ans)

def main():
    N = int(input())
    X, Y = {}, {}
    for _ in range(N):
        x, y = list(map(int, input().split()))
        if x not in X:
            X[x] = set()
        X[x].add(y)
        if y not in Y:
            Y[y] = set()
        Y[y].add(x)
    r = 0
    while X:
        x, ys = X.popitem()
        tx = set([x])
        ty = set(ys)
        mx, my = set(), set(ys)
        while mx or my:
            while my:
                y = my.pop()
                mx |= Y.pop(y, set())
            tx |= mx
            while mx:
                x = mx.pop()
                my |= X.pop(x, set())
            ty |= my
        r += len(tx) * len(ty)
    return r - N

print((main()))


import sys
from collections import defaultdict

class UnionFind:
    def __init__(self, n):
        self.root = [i for i in range(n)]
        self.rank = [0] * n

    def find(self, x):
        if self.root[x] == x:
            return x
        else:
            y = self.find(self.root[x])
            self.root[x] = y
            return self.root[x]

    def unite(self, x, y):
        if self.is_same(x, y):
            return
        rx = self.find(x)
        ry = self.find(y)
        if self.rank[rx] < self.rank[ry]:
            self.root[rx] = ry
        else:
            self.root[ry] = rx
            if self.rank[rx] == self.rank[ry]:
                self.rank[rx] += 1

    def is_same(self, x, y):
        return self.find(x) == self.find(y)
    

def main():
    input = sys.stdin.readline
    V = 10**5  # max size of x
    N = int(input())
    uft = UnionFind(2*V)
    for _ in range(N):
        x, y = list(map(int, input().split()))
        x -= 1
        y += V - 1
        uft.unite(x, y)

    cnt_x = defaultdict(lambda: 0)
    cnt_y = defaultdict(lambda: 0)
    for i in range(2*V):
        root = uft.find(i)
        if i < V:  # node of x coordinate.
            cnt_x[root] += 1
        else:
            cnt_y[root] += 1

    ans = 0
    for root in list(cnt_x.keys()):
        ans += cnt_x[root] * cnt_y[root]

    return ans - N


def __starting_point():
    print((main()))

__starting_point()

from queue import PriorityQueue
import sys

sys.setrecursionlimit(10**9)

IA = lambda: [int(x) for x in input().split()]
IM = lambda N: [IA() for _ in range(N)]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
N=100005
fa=[int(0) for i in range(2*(N+1))]
xNum=[int(0) for i in range(2*(N+1))]
yNum=[int(0) for i in range(2*(N+1))]

def find(x):
    if(x==fa[x]):return x
    else:
        fa[x]=find(fa[x])
        return fa[x]

n=input()
n=int(n)

for i in range(2*N+1):
    fa[i]=i

for i in range(n):
    x,y=IA()
    y+=N
    fx=int(find(x))
    fy=int(find(y))
 
    if(fx!=fy):
        fa[fy]=fx
for i in range(N):
    xNum[find(i)]+=1
for i in range(N):
    yNum[find(i+N)]+=1
ans=0
for i in range(N):
    ans+=xNum[i]*yNum[i]
print((ans-n))




import os
import sys
from collections import defaultdict

if os.getenv("LOCAL"):
    sys.stdin = open("_in.txt", "r")

sys.setrecursionlimit(2147483647)
INF = float("inf")

N = int(sys.stdin.readline())
X, Y = list(zip(*[list(map(int, sys.stdin.readline().split())) for _ in range(N)]))

yx = defaultdict(lambda: set())
xy = defaultdict(lambda: set())
for x, y in zip(X, Y):
    xy[x].add(y)
    yx[y].add(x)


def connected(x=None, y=None, xret=None, yret=None):
    xret = xret if xret else set()
    yret = yret if yret else set()

    if x is not None:
        for y in xy[x]:
            if y in yret:
                continue
            yret.add(y)
            xret, yret = connected(y=y, xret=xret, yret=yret)
    if y is not None:
        for x in yx[y]:
            if x in xret:
                continue
            xret.add(x)
            xret, yret = connected(x=x, xret=xret, yret=yret)

    return xret, yret


x_counted = set()
y_counted = set()

# X と Y の二部グラフで、連結であるすべての組に辺を張る
ans = 0
for x in list(xy.keys()):
    if x in x_counted:
        continue
    xs, ys = connected(x=x)
    # 辺数
    ans += len(xs) * len(ys)
    x_counted |= xs
    y_counted |= ys
# すでにある辺が重複してる
ans -= N
print(ans)


n=int(input())
xy=[list(map(int,input().split())) for _ in range(n)]
maxx=max([x for x,y in xy])
maxy=max([y for x,y in xy])
g=[[] for _ in range(maxx+maxy+1)]
for x,y in xy:
  y+=maxx
  g[x].append(y)
  g[y].append(x)
# 1より大きい奇数回で到達可能な頂点にパスを追加していく。最短距離が3以上の奇数の頂点のペアの数。奇数のパスを数え、最後にパスの数を引く
mi=set(range(maxx+maxy+1))
ans=0
while mi:
  v=mi.pop()
  todo=[v]
  nx=0
  ny=0
  while todo:
    v=todo.pop()
    if v<=maxx:
      nx+=1
    else:
      ny+=1
    for nv in g[v]:
      if nv in mi:
        mi.discard(nv)
        todo.append(nv)
  ans+=nx*ny
print((ans-n))


import sys
from collections import deque
input = sys.stdin.buffer.readline
sys.setrecursionlimit(10 ** 7)

N = int(input())
U = 10 ** 5
edge = [[] for _ in range(2 * U + 1)]
for _ in range(N):
    x, y = map(int, input().split())
    y += U
    edge[x].append(y)
    edge[y].append(x)

ans = 0
seen = [False] * (2 * U + 1)
for i in range(1, 2 * U + 1):
    if not edge[i] or seen[i]:
        continue
    seen[i] = True
    node = deque([i])
    x = 0
    y = 0
    while node:
        s = node.popleft()
        if s <= U:
            x += 1
        else:
            y += 1
        for t in edge[s]:
            if seen[t]:
                continue
            seen[t] = True
            node.append(t)
    ans += x * y

ans = ans - N
print(ans)

def main():
    n=int(input())
    xy=[list(map(int,input().split())) for _ in [0]*n]
    ixy=[[i,x,y] for i,(x,y) in enumerate(xy)]
    class unionfind():
        #size:要素数,tree：unionfind木
        def __init__(self,size):#self,要素数
            self.size=size
            self.tree_root=list(range(self.size))
            self.tree_depth=[1]*self.size
        
        #rootを探す
        def root(self,index):
            temp_list=[]
            temp=self.tree_root[index]
            while index!=temp:
                temp_list.append(index)
                index=temp
                temp=self.tree_root[index]
            for i in temp_list:
                self.tree_root[i]=index
            return index
        
        #結合
        def unite(self,index1,index2):
            r1=self.root(index1)
            r2=self.root(index2)
            if r1!=r2:
                d1,d2=self.tree_depth[r1],self.tree_depth[r2]
                if d1<=d2:
                    self.tree_root[r1]=r2
                    self.tree_depth[r2]=max(d1+1,d2)
                else:
                    self.tree_root[r2]=r1
                    self.tree_depth[r1]=max(d2+1,d1)
    
        #同じか判定
        def same(self,index1,index2):
            r1=self.root(index1)
            r2=self.root(index2)
            return r1==r2
    
        #連結成分の個数
        def component(self):
            return len({self.root(i) for i in range(self.size)})

    u=unionfind(n)
    ixy.sort(key=lambda x:x[1])
    for i in range(n-1):
        if ixy[i][1]==ixy[i+1][1]:
            u.unite(ixy[i][0],ixy[i+1][0])
    ixy.sort(key=lambda x:x[2])
    for i in range(n-1):
        if ixy[i][2]==ixy[i+1][2]:
            u.unite(ixy[i][0],ixy[i+1][0])
    tate=[set() for _ in [0]*n]
    yoko=[set() for _ in [0]*n]
    cnt=[0]*n
    for i in range(n):
        r=u.root(i)
        tate[r].add(xy[i][0])
        yoko[r].add(xy[i][1])
        cnt[r]+=1
    print(sum([len(tate[i])*len(yoko[i])-cnt[i] for i in range(n)]))
main()

from collections import deque
N = int(input())
V = set()
E = {}

for _ in range(N):
  x,y = map(int, input().split())
  y += 100010
  V.add(x)
  V.add(y)
  if x not in E:
    E[x] = [y]
  else:
    E[x].append(y)
  if y not in E:
    E[y] = [x]
  else:
    E[y].append(x)

#print(V)
#print(E)
## DFS
visited = [ False ] * 200020
willSearch = [ False ] * 200020
Q = deque()
ans = 0
for v in V:
  if visited[v]:
    continue

  Q.append(v)
  xcount, ycount = 0,0
  edge_count = 0
  while len(Q) > 0:
    now = Q.pop()
    visited[now] = True

    if now > 100005:
      ycount += 1
    else:
      xcount += 1
      
    for nxt in E[now]:
      edge_count += 1
      if visited[nxt] or willSearch[nxt]:
        continue
      #print(v,now,nxt)

      willSearch[nxt] = True

      Q.append(nxt)
  #print(v,xcount, ycount, edge_count)
  ans += xcount * ycount
  ans -= edge_count // 2
  
print(ans)

def main():
    from collections import defaultdict
    import sys
    # functions used
    r = lambda: sys.stdin.readline().strip()
    R = lambda: list(map(int, r().split()))

    N = int(r())
    Dic = defaultdict(list)
    V = 10**5 + 5
    Arrived = [False] * (2 * V)
    for _ in range(N):
        x, y = R()
        y += V
        Dic[x].append(y)
        Dic[y].append(x)

    ans = 0
    task = []
    for k, v in Dic.items():
        if Arrived[k]:
            continue
        Arrived[k] = True
        if k < V:
            cntx = 1
            cnty = 0
        else:
            cntx = 0
            cnty = 1
        cntdot = 0
        task.extend(v)
        cntdot += len(v)
        while task:
            s = task.pop()
            if Arrived[s]:
                continue
            if s < V:
                cntx += 1
            else:
                cnty += 1
            Arrived[s] = True
            task.extend(Dic[s])
            cntdot += len(Dic[s])
        ans += cntx * cnty - cntdot // 2
    print(ans)

def __starting_point():
    main()
__starting_point()

class UnionFind:
    def __init__(self, n: int) -> None:
        #マイナスならルートでその深さを表す。プラスならルートのノードを指す。
        self.nodes = [-1]*n
 
    def find_root(self, x: int) -> int:
        if self.nodes[x] < 0:
            return x
        else:
            self.nodes[x] = self.find_root(self.nodes[x])
            return self.nodes[x]
 
    def unite(self, x: int, y: int) -> None:
        x, y = self.find_root(x), self.find_root(y)
        #浅い方につなぐ
        if (x != y):
            if (x < y):
                self.nodes[y] += self.nodes[x]
                self.nodes[x] = y
            else:
                self.nodes[x] += self.nodes[y]
                self.nodes[y] = x
                
    def get_num(self, x: int) -> int:
        return -(self.nodes[x])

MAX = 10**5+1

N = int(input())
uf = UnionFind(2*MAX)

for i in range(N):
  x,y = list(map(int, input().split()))
  uf.unite(x,y+MAX)

#数え上げ
num_x = [0]*2*MAX
num_y = [0]*2*MAX
for i in range(MAX):
  num_x[uf.find_root(i)] += 1
  num_y[uf.find_root(i+MAX)] += 1
  #print(num_x[uf.find_root(i)], num_y[uf.find_root(i+MAX)])

ans = 0
for i in range(2*MAX):
  ans += num_x[i] * num_y[i]

print((ans - N))


class UnionFind:
    def __init__(self, n, t, s):
        self.par = [i for i in range(n+1)]
        self.rank = [0] * (n + 1)
        self.sizex = [1] * t + [0] * s
        self.sizey = [0] * t + [1] * s

    # 検索
    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def sizeof(self, x):
        return self.sizex[self.find(x)] * self.sizey[self.find(x)]

    # 併合
    def unite(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
            self.sizex[y] += self.sizex[x]
            self.sizey[y] += self.sizey[x]
        else:
            self.par[y] = x
            self.sizex[x] += self.sizex[y]
            self.sizey[x] += self.sizey[y]
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    # 同じ集合に属するか判定
    def same_check(self, x, y):
        return self.find(x) == self.find(y)


def coordinate_compression(arr):
    return {v: k for k, v in enumerate(sorted(set(arr)))}


n = int(input())
x = [0] * n
y = [0] * n
for i in range(n):
    x[i], y[i] = list(map(int, input().split()))
dx = coordinate_compression(x)
dy = coordinate_compression(y)
uf = UnionFind(len(dx) + len(dy), len(dx), len(dy))
for i in range(n):
    uf.unite(dx[x[i]], dy[y[i]] + len(dx))
s = set()
ans = 0
for i in range(len(dx)):
    p = uf.find(i)
    if p in s:
        continue
    ans += uf.sizeof(i)
    s.add(p)
print((ans - n))


# import sys
# sys.setrecursionlimit(20000)


N = int(input())
COR = [tuple((list(map(int, input().split())))) for _ in range(N)]


class Group:
    
    def __init__(self):
        self.n = 0
        self.x = 0
        self.y = 0
        self.xy = {}
        
    def new_cor_n(self):
        return self.x * self.y - self.n
        

def grouping(group, x, xcor, ycor):
    x_list = []
    group.x += 1
    for y in xcor[x][0]:
        if (x, y) not in group.xy:
            group.n += 1
            group.xy[(x, y)] = None
        if ycor[y][1]:
            continue
        ycor[y][1] = True
        group.y += 1
        for x_ in ycor[y][0]:
            if (x_, y) not in group.xy:
                group.n += 1
                group.xy[(x_, y)] = None
            if xcor[x_][1]:
                continue
            xcor[x_][1] = True
            x_list.append(x_)
    return x_list

if N <= 2:
    print((0))
else:
    xcor, ycor = {}, {}
    for xi, yi in COR:
        if xi in xcor:
            xcor[xi][0].append(yi)
        else:
            xcor[xi] = [[yi], False]
        if yi in ycor:
            ycor[yi][0].append(xi)
        else:
            ycor[yi] = [[xi], False]
    group_list = []
    for i, v in list(xcor.items()):
        if v[1]:
            continue
        group = Group()
        group_list.append(group)
        xcor[i][1] = True
        x_list = [i]
        while True:
            new_x_list = []
            for x in x_list:
                new_x_list += grouping(group, x, xcor, ycor)
            if new_x_list == []:
                break
            x_list = new_x_list
    s = 0
    for group in group_list:
        s += group.new_cor_n()
    print(s)


import sys
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines

import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import connected_components

"""
点(x,y) を、辺x-yと見て二部グラフとみなす
"""

N = int(readline())
XY = np.array(read().split(),np.int32)

U = 10**5 + 100
X = XY[::2]; Y = XY[1::2]
graph = csr_matrix((np.ones(N),(X,Y+U)),(U+U,U+U))
Ncomp,comp = connected_components(graph)

comp_x=np.bincount(comp[:U],minlength=Ncomp) # 連結成分に含まれるx座標の個数
comp_y=np.bincount(comp[U:],minlength=Ncomp) # 連結成分に含まれるx座標の個数
comp_E=np.bincount(comp[X],minlength=Ncomp) # 連結成分に含まれる辺の個数

answer = (comp_x * comp_y - comp_E).sum()
print(answer)

import sys
input = sys.stdin.readline
inpl = lambda: list(map(int,input().split()))
from collections import defaultdict

class UnionFind:
    def __init__(self, N=None):
        if N is None or N < 1:
            self.parent = defaultdict(lambda: -1)
        else:
            self.parent = [-1]*int(N)

    def root(self, n):
        if self.parent[n] < 0:
            return n
        else:
            m = self.root(self.parent[n])
            self.parent[n] = m
            return m

    def merge(self, m, n):
        rm = self.root(m)
        rn = self.root(n)
        if rm != rn:
            if -self.parent[rm] < -self.parent[rn]:
                rm, rn = rn, rm
            self.parent[rm] += self.parent[rn]
            self.parent[rn] = rm

    def size(self, n):
        return -self.parent[self.root(n)]
    
    def connected(self, m, n):
        return self.root(m) == self.root(n)
    
    def groups(self):
        if isinstance(self.parent,list):
            return list(map(lambda x: x<0, self.parent)).count(True)
        else: # self.parent: defaultdict
            return list(map(lambda x: x<0, self.parent.values())).count(True) 

N = int(input())
uf = UnionFind()
for _ in range(N):
    x, y = inpl()
    uf.merge(2*x,2*y+1)

nxny = defaultdict(lambda: [0, 0])
for k in uf.parent.keys():
    nxny[uf.root(k)][k % 2] += 1

ans = sum([ nx * ny for nx, ny in nxny.values() ]) - N
print(ans)

def examA():
    N = DI()/dec(7)
    ans = N
    print(N)
    return

def examB():
    ans = 0
    print(ans)
    return

def examC():
    ans = 0
    print(ans)
    return

def examD():
    ans = 0
    print(ans)
    return

def examE():
    ans = 0
    print(ans)
    return

def examF():
    # xとyに分けるとなんか二部グラフが見えてくる
    # 違うんか。。
    def bfs(V,used,s=0):
        W = {}
        W[s] = 1
        que = deque([s])
        while(que):
            now = que.pop()
            used[now] = True
            ne = W[now]*(-1)
            for v in V[now]:
                if v not in W:
                    W[v] = ne
                    que.append(v)
                elif W[v]!=ne:
                    return False
        return W,used
    y_shift = 10**5 + 1
    N = I()
    V = defaultdict(set)
    for _ in range(N):
        x, y = LI()
        y += y_shift
        V[x].add(y)
        V[y].add(x)

    ans = 0
    used = defaultdict(lambda:False)
    for i in V.keys():
        if used[i]:
            continue
        W,used = bfs(V,used,i)
        x = 0
        for w in W.values():
            if w==1:
                x += 1
        y = len(W)-x
        #print(W,x,y)
        ans += x*y
    ans = max(0,ans-N)
    print(ans)
    return

from decimal import getcontext,Decimal as dec
import sys,bisect,itertools,heapq,math,random
from copy import deepcopy
from heapq import heappop,heappush,heapify
from collections import Counter,defaultdict,deque
read = sys.stdin.buffer.read
readline = sys.stdin.buffer.readline
readlines = sys.stdin.buffer.readlines
def I(): return int(input())
def LI(): return list(map(int,sys.stdin.readline().split()))
def DI(): return dec(input())
def LDI(): return list(map(dec,sys.stdin.readline().split()))
def LSI(): return list(map(str,sys.stdin.readline().split()))
def LS(): return sys.stdin.readline().split()
def SI(): return sys.stdin.readline().strip()
nonlocal mod,mod2,inf,alphabet,_ep
mod = 10**9 + 7
mod2 = 998244353
inf = 10**18
_ep = dec("0.000000000001")
alphabet = [chr(ord('a') + i) for i in range(26)]
alphabet_convert = {chr(ord('a') + i): i for i in range(26)}

getcontext().prec = 28

sys.setrecursionlimit(10**7)

def __starting_point():
    examF()

"""
142
12 9 1445 0 1
asd dfg hj o o
aidn
"""
__starting_point()

V=10**5+5
class UnionFind:
    def __init__(self,n):
        self.parent=[i for i in range(n)]
        self.rank=[0 for _ in range(n)]
        # use for n=2*V
        self.xSize=[1 if i<V else 0 for i in range(n)]
        self.ySize=[0 if i<V else 1 for i in range(n)]

    def find(self,x):
        if self.parent[x]==x:
            return x
        else:
            parentx=self.find(self.parent[x])
            self.parent[x]=parentx
            return parentx

    def union(self,x,y):
        rootx=self.find(x)
        rooty=self.find(y)
        if rootx==rooty:
            return
        if self.rank[rootx]>self.rank[rooty]:
            self.parent[rooty]=rootx
            self.xSize[rootx]+=self.xSize[rooty]
            self.ySize[rootx]+=self.ySize[rooty]
        else:
            self.parent[rootx]=rooty
            self.xSize[rooty]+=self.xSize[rootx]
            self.ySize[rooty]+=self.ySize[rootx]
            if self.rank[rootx]==self.rank[rooty]:
                self.rank[rooty]+=1

n=int(input())
xy=[tuple(map(int,input().split())) for _ in range(n)]
uf=UnionFind(V*2)
for xi,yi in xy:
    uf.union(xi,yi+V)

# for i in range(V*2):
#     uf.find(i)

ans=0
used=set()
for i in range(V*2):
    rooti=uf.find(i)
    if rooti not in used:
        used.add(rooti)
        ans+= uf.xSize[rooti]*uf.ySize[rooti]
print((ans-n))


from collections import Counter

class UnionFind:
    def __init__(self, size):
        self.data = [-1] * size

    def find(self, x):
        if self.data[x] < 0:
            return x
        else:
            self.data[x] = self.find(self.data[x])
            return self.data[x]

    def union(self, x, y):
        x, y = self.find(x), self.find(y)
        if x != y:
            if self.data[y] < self.data[x]:
                x, y = y, x
            self.data[x] += self.data[y]
            self.data[y] = x
        return (x != y)

    def same(self, x, y):
        return (self.find(x) == self.find(y))

    def size(self, x):
        return -self.data[self.find(x)]


MAX = 10 ** 5 + 10
uf = UnionFind(2 * MAX)

N, *XY = list(map(int, open(0).read().split()))

for x, y in zip(*[iter(XY)] * 2):
    uf.union(x, y + MAX)

X = Counter(uf.find(i) for i in range(MAX))
Y = Counter(uf.find(i) for i in range(MAX, MAX * 2))
res = sum(X[i] * Y[i] for i in range(MAX * 2))

print((res - N))


def main():
    from collections import deque
    import sys
    input = sys.stdin.readline

    M = 10 ** 5

    N = int(input())

    g = tuple(set() for _ in range(M * 2))
    for _ in range(N):
        x, y = (int(x) - 1 for x in input().split())  # 一律に移動
        g[x].add(y + M)
        g[y + M].add(x)

    visited = [0] * (M * 2)
    ans = 0
    for s in range(M * 2):
        if visited[s]: continue
        visited[s] = 1
        dq = deque()
        dq.append(s)

        sz = 0
        x = 0
        e = 0
        while dq:
            v = dq.popleft()
            sz += 1
            if v < M:
                x += 1
            for u in g[v]:
                e += 1
                if visited[u]: continue
                visited[u] = 1
                dq.append(u)
        ans += (sz - x) * x - (e // 2)
        # ans += (sz - x) * x - (sz - 1) # sz-1は木に限られるのでWA

    print(ans)


def __starting_point():
    main()

__starting_point()

import sys
input = sys.stdin.readline
inpl = lambda: list(map(int,input().split()))
from collections import defaultdict

class UnionFind:
    def __init__(self, N=None):
        if N is None or N < 1:
            self.parent = defaultdict(lambda: -1)
        else:
            self.parent = [-1]*int(N)

    def root(self, n):
        if self.parent[n] < 0:
            return n
        else:
            m = self.root(self.parent[n])
            self.parent[n] = m
            return m

    def merge(self, m, n):
        rm = self.root(m)
        rn = self.root(n)
        if rm != rn:
            if -self.parent[rm] < -self.parent[rn]:
                rm, rn = rn, rm
            self.parent[rm] += self.parent[rn]
            self.parent[rn] = rm

    def size(self, n):
        return -self.parent[self.root(n)]
    
    def connected(self, m, n):
        return self.root(m) == self.root(n)
    
    def groups(self):
        if isinstance(self.parent,list):
            return list(map(lambda x: x<0, self.parent)).count(True)
        else: # self.parent: defaultdict
            return list(map(lambda x: x<0, self.parent.values())).count(True) 

class CountUp:
    def __init__(self, start=0):
        self.index = start-1

    def __call__(self):
        self.index += 1
        return self.index

N = int(input())
uf = UnionFind()
for _ in range(N):
    x, y = inpl()
    uf.merge(2*x+1,2*y)

Ng = uf.groups()
nx = [0]*Ng
ny = [0]*Ng
gi = defaultdict(CountUp())
for k in uf.parent.keys():
    if k % 2:
        nx[gi[uf.root(k)]] += 1
    else:
        ny[gi[uf.root(k)]] += 1

ans = sum([ nx[g]*ny[g] for g in range(Ng) ]) - N
print(ans)

def main():
    from collections import deque
    import sys
    input = sys.stdin.readline

    M = 10 ** 5

    N = int(input())

    g = tuple(set() for _ in range(M * 2))
    ps = set()
    for _ in range(N):
        x, y = (int(x) - 1 for x in input().split())  # 一律に移動
        g[x].add(y + M)
        g[y + M].add(x)
        ps.add(x)
        ps.add(y + M)

    visited = [0] * (M * 2)
    ans = 0
    for s in ps:
        if visited[s]: continue
        visited[s] = 1
        dq = deque()
        dq.append(s)

        sz = 0
        x = 0
        while dq:
            v = dq.popleft()
            sz += 1
            if v < M:
                x += 1
            for u in g[v]:
                if visited[u]: continue
                visited[u] = 1
                dq.append(u)
        ans += (sz - x) * x

    ans -= N

    print(ans)


def __starting_point():
    main()

# 連結成分ごとに
# x,yの二部グラフを完全二部グラフにする
# cnt(x)*cnt(y)-既存の辺数の総和

# 最後にN引けばよい

__starting_point()

class UnionFindTree:
    """Disjoint-Set Data Structure
       Union-Find Tree
       complexity:
          init: O(n)
          find, unite, same: O(alpha(n))
       used in SRM505 div.2 900, ATC001 A, DSL1A(AOJ)
    """
    def __init__(self, n):
        self.par = list(range(n))  # parent
        self.rank = [0] * n  # depth of tree

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def unite(self, x, y):
        x, y = self.find(x), self.find(y)
        if x == y:
            return
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
        else:
            self.par[y] = x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    def same(self, x, y):
        return self.find(x) == self.find(y)


N = int(input())
M = 10 ** 5
uft = UnionFindTree(N + 2 * M)

for i in range(N):
    x, y = list(map(int, input().split()))
    x, y = x-1, y-1
    uft.unite(i, x + N)
    uft.unite(i, y + N + M)
x_dict, y_dict = dict(), dict()
for x in range(N, M + N):
    par_x = uft.find(x)
    x_dict[par_x] = x_dict.get(par_x, 0) + 1
for y in range(N + M, N + 2 * M):
    par_y = uft.find(y)
    y_dict[par_y] = y_dict.get(par_y, 0) + 1
ans = -N
for r in list(x_dict.keys()):
    ans += x_dict.get(r, 0) * y_dict.get(r, 0)
print(ans)


import sys
sys.setrecursionlimit(1000000)

N = int(input().strip())

X = {}
Y = {}
G = (X, Y)

for _ in range(N):
  x, y = list(map(int, input().strip().split()))
  if x not in X: X[x] = []
  if y not in Y: Y[y] = []
  X[x].append(y)
  Y[y].append(x)


def dfs_visit(visited, u, i):
  if u in visited[i]: return
  visited[i].add(u)
  for v in G[i][u]:
    dfs_visit(visited, v, (i + 1) % 2)


count = 0
i = 0
while len(X) > 0:
  visited = (set(), set())
  x = X.__iter__().__next__()
  dfs_visit(visited, x, 0)

  for x in visited[0]:
    count += len(visited[1]) - len(X.pop(x))

print(count)


# reference -> https://atcoder.jp/contests/abc131/submissions/10358162
import sys
sys.setrecursionlimit(100000)

# 再帰的に連結部分の辺の数を求める
# 同じ辺を2回ずつ数えるので、最終的には2で割る
def solve(x, x_set, y_set, x2y, y2x):
  num_e = 0
  if x in x2y:
    x_set.add(x)
    yl = x2y.pop(x)
    num_e += len(yl)
    for y in yl:
      num_e += solve(y, y_set, x_set, y2x, x2y)
  return num_e


N = int(input())
X2Y = {}
Y2X = {}
for i in range(N):
  x, y = map(int, input().split())
  if x in X2Y:
    X2Y[x].append(y)
  else:
    X2Y[x] = [y]
  if y in Y2X:
    Y2X[y].append(x)
  else:
    Y2X[y] = [x]


ans = 0
while X2Y:
    x = next(iter(X2Y)) # まだスタートに選んでいないxを取り出す
    x_set = set() # 上のxと連結であるxの集合
    y_set = set() # 上のxと連結であるyの集合
    num_e = solve(x, x_set, y_set, X2Y, Y2X)
    ans += len(x_set)*len(y_set) - num_e//2

print(ans)

#!/usr/bin/env python3
import sys
from collections.abc import Iterable
from math import *
from itertools import *
from collections import *
from functools import *
from operator import *
try:
    from math import gcd
except Exception:
    from fractions import gcd


class DGraph:
    def __init__(self, n, edges):
        self._vertice = [{} for _ in range(n)]
        for e in edges:
            self._vertice[e[0]][e[1]] = e[2] if len(e) == 3 else 1

    def __len__(self):
        return len(self._vertice)

    def edges(self, n=None):
        srcs = list(range(len(self))) if n is None else [n]
        return sum([[(s, d, c) for d, c in list(self._vertice[s].items())] for s in srcs], [])

    def transpose(self):
        tedges = [(t, f, c) for f, t, c in self.edges()]
        return DGraph(len(self), tedges)

    def cost(self, src, dst):
        return self._vertice[src].get(dst, float('inf'))

class Graph(DGraph):
    def __init__(self, n, edges):
        nd_edges = edges + [(d, s, c) for s, d, c in edges]
        super().__init__(n, nd_edges)

    def transpose(self):
        return self


def solve(N: int, x: "List[int]", y: "List[int]"):
    xs = {v:i for i, v in enumerate(set(x))}
    ys = {v:i for i, v in enumerate(set(y), len(xs))}
    
    n = len(xs) + len(ys)
    es = [(xs[cx], ys[cy], 1) for cx, cy in zip(x, y)]
    g = Graph(n, es)

    visited = [False] * len(g)
    def dfs(g, src):
        nonlocal visited
        stack = [(src, 0)]
        while stack:
            n, c = stack.pop()
            if visited[n]:
                continue
            visited[n] = True

            for _, d, _ in g.edges(n):
                #yield d
                stack.append((d, c + 1))
            yield n, c

    def _f(i):
        ls = list(i)
        if len(ls) == 0:
            return 0

        xc = 0
        yc = 0
        es = 0
        for i, _ in ls:
            es += len(g.edges(i))
            if i < len(xs):
                xc += 1
            else:
                yc += 1
        es //= 2

        if xc < 2 or yc < 2:
            return 0

        return xc * yc - es

    return sum([_f(dfs(g, i)) for i in range(n)])


def main():
    def iterate_tokens():
        for line in sys.stdin:
            for word in line.split():
                yield word
    tokens = iterate_tokens()
    N = int(next(tokens))  # type: int
    x = [int()] * (N)  # type: "List[int]" 
    y = [int()] * (N)  # type: "List[int]" 
    for i in range(N):
        x[i] = int(next(tokens))
        y[i] = int(next(tokens))
    result = solve(N, x, y)
    if isinstance(result, Iterable) and not isinstance(result, str):
        result = '\n'.join([str(v) for v in result])
    print(result)

def __starting_point():
    main()

__starting_point()

import sys
sys.setrecursionlimit(10**6)

def dfs(x, al):
    nonlocal cntx, cnty
    if (al, x) in used:
        return
    used.add((al, x))
    if al == 0:
        cntx += 1
    else:
        cnty += 1
    for xy in to[al][x]:
        dfs(xy, 1 - al)
    return

n = int(input())
to = [{}, {}]
for _ in range(n):
    x, y = list(map(int, input().split()))
    to[0].setdefault(x, [])
    to[1].setdefault(y, [])
    to[0][x] += [y]
    to[1][y] += [x]
used = set()
ans = 0
for x in list(to[0].keys()):
    if (0, x) in used: continue
    cntx = 0
    cnty = 0
    dfs(x, 0)
    ans += cntx * cnty
print((ans - n))


import sys
sys.setrecursionlimit(10**6)

def main():
    input = sys.stdin.readline
    V = 10**5  # max size of x
    N = int(input())
    bgraph = [[] for _ in range(2*V)]
    for _ in range(N):
        x, y = list(map(int, input().split()))
        x -= 1
        y += V - 1
        bgraph[x].append(y)
        bgraph[y].append(x)

    def dfs(i):
        if visited[i]:
            return cnt
        visited[i] = True
        cnt[i//V] += 1
        for j in bgraph[i]:
            dfs(j)
        return cnt

    visited = [False] * (2*V)
    ans = 0
    for i in range(V):
        if visited[i]:
            continue
        cnt = [0, 0]
        cnt = dfs(i)
        ans += cnt[0] * cnt[1]

    ans -= N
    return ans


def __starting_point():
    print((main()))

__starting_point()

import sys, math
from collections import defaultdict, deque, Counter
from bisect import bisect_left, bisect_right
from itertools import combinations, permutations, product
from heapq import heappush, heappop
from functools import lru_cache
input = sys.stdin.readline
rs = lambda: input().strip()
ri = lambda: int(input())
rl = lambda: list(map(int, input().split()))
mod = 1000000007
sys.setrecursionlimit(1000000)

N = ri()
to = defaultdict(list)
V = 100005
X = []
for i in range(N):
	x, y = rl()
	X.append(x)
	y += V
	to[x].append(y)
	to[y].append(x)

visited = set()
nx, ny = 0, 0
def dfs(n):
	nonlocal nx, ny
	if n in visited: return
	visited.add(n)
	if n < V:
		nx += 1
	else:
		ny += 1
	for m in to[n]:
		dfs(m)

ans = 0
for x in X:
	if x in visited: continue
	nx, ny = 0, 0
	dfs(x)
	ans += nx*ny
print((ans-N))



import sys
sys.setrecursionlimit(2147483647)
INF=float("inf")
MOD=10**9+7
input=lambda :sys.stdin.readline().rstrip()
def resolve():
    V=100005
    n=int(input())
    E=[[] for _ in range(V*2)]
    for _ in range(n):
        x,y=map(int,input().split())
        y+=V
        E[x].append(y)
        E[y].append(x)

    visited=[0]*(2*V)
    def dfs(v):
        if(visited[v]): return
        visited[v]=1
        cnt[v//V]+=1
        for nv in E[v]: dfs(nv)

    ans=[0]
    for v in range(2*V):
        if(visited[v]): continue
        cnt=[0]*2 # 連結成分のx,yの個数
        dfs(v)
        ans[0]+=cnt[0]*cnt[1]
    print(ans[0]-n)
resolve()

class DisjointSet:
    def __init__(self, size):
        self.rank = [0 for i in range(size)]
        self.p = [0 for i in range(size)]
        for i in range(size):
            self.makeSet(i)
    
    def makeSet(self, x):
        self.p[x] = x
        self.rank[x] = 0

    def same(self, x, y):
        return self.findSet(x) == self.findSet(y)

    def unite(self, x, y):
        self.link(self.findSet(x), self.findSet(y))

    def link(self, x, y):
        if self.rank[x] > self.rank[y]:
            self.p[y] = x
        else:
            self.p[x] = y
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1
    
    def findSet(self, x):
        if x != self.p[x]:
            self.p[x] = self.findSet(self.p[x])
        return self.p[x]
        

def __starting_point():
    maxxy = 10**5
    N = int(input())
    ds = DisjointSet(maxxy*2+1)
    left = [0 for i in range(maxxy*2+1)]
    right = [0 for i in range(maxxy*2+1)]
    for i in range(N):
        x, y = list(map(int, input().split()))
        ds.unite(x, maxxy+y)
    for i in range(maxxy+1):
        left[ds.findSet(i)] += 1
    for i in range(maxxy+1, maxxy*2+1):
        right[ds.findSet(i)] += 1
    ans = 0
    for i in range(maxxy*2+1):
        ans += left[i] * right[i]
    ans -= N
    print(ans)

__starting_point()

import sys
sys.setrecursionlimit(10 ** 7)

def dfs(G, F, crr, pre, ret, st):
    F[crr] = False
    if crr < 10**5:
        ret[0] += 1
    else:
        ret[1] += 1
    for nxt in G[crr]:
        if nxt == pre:
            continue
        p, q = crr, nxt
        if q < p:
            p, q = q, p
        if not (p, q) in st:
            st.add((p, q))
            ret[2] += 1
        if F[nxt]:
            dfs(G, F, nxt, crr, ret, st)

def main():
    n = int(input())
    G = [[] for _ in range(2 * 10**5)]
    for _ in range(n):
        x, y = map(int, input().split())
        x, y = x-1, y-1 + 10**5
        G[x].append(y)
        G[y].append(x)
    F = [True]*(2* 10**5)
    ans = 0
    st = set()
    for i in range(2 * 10**5):
        if F[i] and len(G[i]) > 0:
            tmp = [0]*3
            dfs(G, F, i, -1, tmp, st)
            ans += tmp[0] * tmp[1] - tmp[2]
    print(ans)

def __starting_point():
    main()
__starting_point()

import sys
sys.setrecursionlimit(10 ** 7)
n = int(input())
edges = [[] for i in range(200000)]
for i in range(n):
    x, y = map(int, input().split())
    edges[x - 1].append(y + 99999)
    edges[y + 99999].append(x - 1)
ans = 0
visited = [0] * (200000)
cntx, cnty = 0, 0
def dfs(u):
    nonlocal cntx, cnty
    visited[u] = 1
    if u // 100000 >= 1:
        cnty += 1
    else:
        cntx += 1
    for i in edges[u]:
        if visited[i] == 0:
            dfs(i)
ans = -1 * n
for i in range(100000):
    if visited[i] == 0:
        cntx, cnty = 0, 0
        dfs(i)
        ans += cntx * cnty
print(ans)

# https://drken1215.hatenablog.com/entry/2019/06/23/004700
# https://tjkendev.github.io/procon-library/python/union_find/union_find.html

def main():
    import sys
    input = sys.stdin.readline

    MX = 10 ** 5 + 10
    
    *p, = [r for r in range(MX * 2)]

    def root(x):
        if x == p[x]:
            return x
        p[x] = y = root(p[x])
        return y

    def unite(x, y):
        px = root(x)
        py = root(y)
        if px == py:
            return 0
        if px < py:
            p[py] = px
        else:
            p[px] = py
        return 1

    n = int(input())
    for _ in range(n):
        x, y = (int(x) - 1 for x in input().split())  # [1,)->[0,)
        unite(x, y + MX)

    xs = [0] * (MX * 2)
    ys = [0] * (MX * 2)

    for r in range(MX):
        xs[root(r)] += 1

    for r in range(MX, MX * 2):
        ys[root(r)] += 1

    ans = -n
    for r in range(MX * 2):
        ans += xs[r] * ys[r]

    print(ans)


def __starting_point():
    main()

__starting_point()

from collections import defaultdict,deque
import sys,heapq,bisect,math,itertools,string,queue
sys.setrecursionlimit(10**8)
INF = float('inf')
mod = 10**9+7
eps = 10**-7
def inpl(): return list(map(int, input().split()))
def inpl_str(): return list(input().split())

class UnionFind:
    def __init__(self,N): # 頂点数 N
        self.table = [i for i in range(N)]    #木の親 table[x] == x なら根
        self.rank  = [1 for i in range(N)]    #木の長さ
        self.size  = [1 for i in range(N)]    #集合のサイズ

    def Find(self,x):    #xの根を返す
        if self.table[x] == x:
            return x
        else:
            self.table[x] = self.Find(self.table[x])    #親の更新(根を直接親にして参照距離を短く)
            self.size[x] = self.size[self.table[x]]
            return self.table[x]

    def Unite(self,x,y):    #xとyを繋げる
        x,y = self.Find(x), self.Find(y)
        sx,sy = self.Size(x), self.Size(y)
        if x == y: return
        if self.rank[x] > self.rank[y]:
            self.table[y] = x
            self.size[x] = sx + sy
        else:
            self.table[x] = y
            self.size[y] = sx + sy
            if self.rank[x] == self.rank[y]:
                self.rank[y] += 1

    def Check(self,x,y):
        return self.Find(x) == self.Find(y)

    def Size(self,x):
        return self.size[self.Find(x)]



N = int(input())
UF = UnionFind(10**5+1)

xys = [inpl() for _ in range(N)]
xys.sort()
tatedict = defaultdict(list)
xx = set()
for x,y in xys:
    tatedict[x].append(y)
    xx.add(x)

for x in xx:
    s = tatedict[x][0]
    for t in tatedict[x]:
        if not UF.Check(s,t):
            UF.Unite(s,t)

ans = 0
for x in xx:
    s = tatedict[x][0]
    ans += UF.Size(s) - len(tatedict[x])

print(ans)


def main():
    MAX = 10 ** 5 + 1

    data = [-1] * (2 * MAX)

    def find(x):
        if data[x] < 0:
            return x
        else:
            data[x] = find(data[x])
            return data[x]

    def union(x, y):
        x, y = find(x), find(y)
        if x != y:
            if data[y] < data[x]:
                x, y = y, x
            data[x] += data[y]
            data[y] = x
        return (x != y)


    N, *XY = list(map(int, open(0).read().split()))

    for x, y in zip(*[iter(XY)] * 2):
        union(x, y + MAX)

    X = [0] * (MAX * 2)
    Y = [0] * (MAX * 2)

    for i in range(MAX):
        X[find(i)] += 1
    for i in range(MAX, MAX * 2):
        Y[find(i)] += 1

    res = sum(X[i] * Y[i] for i in range(MAX * 2))

    print((res - N))


def __starting_point():
    main()

__starting_point()

class UnionFindTree:
    """Disjoint-Set Data Structure
       Union-Find Tree
       complexity:
          init: O(n)
          find, unite, same: O(alpha(n))
       used in SRM505 div.2 900, ATC001 A, DSL1A(AOJ)
    """
    def __init__(self, n):
        self.par = list(range(n))  # parent
        self.rank = [0] * n  # depth of tree

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def unite(self, x, y):
        x, y = self.find(x), self.find(y)
        if x == y:
            return
        if self.rank[x] < self.rank[y]:
            self.par[x] = y
        else:
            self.par[y] = x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    def same(self, x, y):
        return self.find(x) == self.find(y)


N = int(input())
M = 10 ** 5
uft = UnionFindTree(2 * M)

for _ in range(N):
    x, y = list(map(int, input().split()))
    x, y = x-1, y-1
    uft.unite(x, y + M)
x_dict, y_dict = dict(), dict()
for x in range(M):
    par_x = uft.find(x)
    x_dict[par_x] = x_dict.get(par_x, 0) + 1
for y in range(M, 2 * M):
    par_y = uft.find(y)
    y_dict[par_y] = y_dict.get(par_y, 0) + 1
ans = -N
for r in list(x_dict.keys()):
    ans += x_dict.get(r, 0) * y_dict.get(r, 0)
print(ans)


from sys import stdin
import sys
import numpy as np
import collections
from functools import cmp_to_key
import heapq
sys.setrecursionlimit(100000)

##  input functions for me
def rsa(sep = ''):
    if sep == '' :
        return input().split() 
    else: return input().split(sep)
def rip(sep = ''):
    if sep == '' :
        return list(map(int, input().split())) 
    else: return list(map(int, input().split(sep)))
def ria(sep = ''): 
    return list(rip(sep))
def ri(): return int(input())
def rd(): return float(input())
def rs(): return input()
##
class union_find:
    def __init__(self, n):
        self.N = n
        self._parent = [i for i in range(self.N)]
        self._mem = [1] * self.N
        self.compo = self.N

    def parent(self, a):
        if self._parent[a] == a:
            return a
        self._parent[a] = self.parent(self._parent[a])
        return self._parent[a]

    def united(self,a, b):
        return self.parent(a) == self.parent(b)
    
    def unite(self,a, b):
        a = self.parent(a)
        b = self.parent(b)
        if(a == b): return False
        if self._mem[a] > self._mem[b]: a, b = b, a
        self._parent[a] = b
        self._mem[b] += self._mem[a]
        self.compo -= 1
        return True
    
    def is_root(self,a):
        return a == self._parent[a]
    def mem_cnt(self,a):
        return self._mem[self.parent(a)]
    def dump(self):
        print((self._parent))



def main():
    N = ri()
    X = [0] * N
    Y = [0] * N
    for i in range(N):
        X[i], Y[i] = rip()
    
    uf = union_find(N)
    di = dict()
    for i in range(N):
        if not X[i] in di: di[X[i]] = i
        uf.unite(i, di[X[i]])
    di.clear()
    for i in range(N):
        if not Y[i] in di: di[Y[i]] = i
        uf.unite(i, di[Y[i]])

    hx = [set() for i in range(N)]
    hy = [set() for i in range(N)]
    for i in range(N):
        hx[uf.parent(i)].add(X[i])
        hy[uf.parent(i)].add(Y[i])
    
    tot = 0
    for i in range(N):
        tot += len(hx[i]) * len(hy[i])
    
    print((tot - N))



def __starting_point():
    main()

__starting_point()

class UnionFind:

  def __init__(self, n):
    self.par = [i for i in range(n)]
    self.rank = [0] * n
    self.sizes = [1] * n

  def find(self, x):
    if self.par[x] != x:
      self.par[x] = self.find(self.par[x])
    return self.par[x]

  def union(self, x, y):
    x, y = self.find(x), self.find(y)
    if x == y:
      return False
    elif self.rank[x] < self.rank[y]:
      self.par[x] = y
      self.sizes[y] += self.sizes[x]
    elif self.rank[x] > self.rank[y]:
      self.par[y] = x
      self.sizes[x] += self.sizes[y]
    else:
      self.par[y] = x
      self.rank[x] += 1
      self.sizes[x] += self.sizes[y]
    return True

  def same(self, x, y):
    return self.find(x) == self.find(y)

  def size(self, x):
    return self.sizes[self.find(x)]

n = int(input())
nx = [0 for i in range(200020)]
ny = [0 for i in range(200020)]
uf = UnionFind(200020)
for i in range(n):
  x, y = list(map(int, input().split()))
  uf.union(x, y+100010)
ans = 0
for i in range(100010):
  nx[uf.find(i)] += 1
  ny[uf.find(i+100010)] += 1
for i in range(200020):
  ans += nx[i]*ny[i]
print(ans-n)

import numpy as np
from scipy.sparse import csr_matrix, csgraph
import sys

input = sys.stdin.readline

U = 10 ** 5
n = int(input())
xy = np.array([input().split() for _ in range(n)], dtype=np.int32)

graph = csr_matrix((np.ones(n, dtype=np.bool), (xy[:, 0], U + xy[:, 1])), (2 * U + 1, 2 * U + 1))
_, component = csgraph.connected_components(graph)

x_cnt = np.bincount(component[:U + 1], minlength=2 * U + 2)
y_cnt = np.bincount(component[U + 1:], minlength=2 * U + 2)
pts_cnt = np.bincount(component[xy[:, 0]], minlength=2 * U + 2)
answer = (x_cnt * y_cnt - pts_cnt).sum()
print(answer)


import sys
sys.setrecursionlimit(100000)

N = int(input())
vertex = set()
conn = [[] for _ in range(2*10**5 + 1)]
check = [False]*(2*10**5 + 1)
for i in range(N):
    x, y = [int(x) for x in input().split()]
    vertex.add(x)
    vertex.add(y + 10**5)
    conn[x].append(y + 10**5)
    conn[y + 10**5].append(x)

count = [0] * 2
def dfs(z):
    if check[z] == True:
        return
    check[z] = True
    if z <= 10**5:
        count[0] += 1
    else:
        count[1] += 1
    for w in conn[z]:
        dfs(w)

ans = 0
for z in vertex:
    if check[z] == False:
        count = [0]*2
        dfs(z)
        X, Y = count[0], count[1]
        ans += X * Y
ans -= N

print(ans)

import sys
input = sys.stdin.readline
def ii(): return int(input())
def mi(): return map(int, input().rstrip().split())
def lmi(): return list(map(int, input().rstrip().split()))
def li(): return list(input().rstrip())
# template

# PyPyだと再帰が遅いので注意
# BEGIN CUT HERE
sys.setrecursionlimit(100000)
class UnionFind():
    '''UnionFind木'''
    def __init__(self,n):
        self.data = [-1] * n
    
    def unite(self, x, y):
        '''x と y を結合'''
        x = self.root(x)
        y = self.root(y)
        if x == y:
            return
        elif self.data[x] < self.data[y]:
            x, y = y, x  # swap
        self.data[x] += self.data[y]
        self.data[y] = x
    
    def root(self, x):
        '''ノード x の親を返す'''
        if self.data[x] < 0:
            return x
        self.data[x] = self.root(self.data[x])
        return self.data[x]

    def same(self, x, y):
        '''x と y が同じグループに属するか判定'''
        return self.root(x) == self.root(y)
    
    def size(self, x):
        '''x を含むグループのノード数を返す'''
        return self.data[self.root(x)]
        
# END CUT HERE

def ABC120_D():
    n, m = mi()
    a = [0] * m
    b = [0] * m
    for i in range(m):
        a[i], b[i] = mi()
        
    uf = UnionFind(n)
    ans = [0] * (m + 1)
    ans[m] = n * (n - 1) // 2

    for i in range(m - 1, -1, -1):
        a[i] -= 1
        b[i] -= 1
        ans[i] = ans[i + 1]
        if uf.same(a[i], b[i]):
            continue
        
        x = uf.size(a[i])
        y = uf.size(b[i])
        ans[i] -= x * y
        
        uf.unite(a[i],b[i])
    
    for i in range(1, m + 1):
        print(ans[i])

# verified on 2019/06/23
# Python3:616ms https://atcoder.jp/contests/abc120/submissions/6094323

def ABC126_E():
    N, M = mi()
    uf = UnionFind(N)
    for i in range(M):
        _X, _Y, _Z = mi()
        uf.unite(_X - 1, _Y - 1)
    ans = 0
    for i in range(N):
        if uf.root(i) == i:
            ans += 1
    print(ans)
# verified on 2019/06/23
# Python3:468ms https://atcoder.jp/contests/abc126/submissions/6094497

def ABC131_F():
    from collections import defaultdict
    MAX = 100010
    uf = UnionFind(2 * MAX)
    N = ii()
    for i in range(N):
        x, y = mi()
        uf.unite(x, y + MAX)
    
    mx = defaultdict(int)
    for i in range(MAX):
        mx[uf.root(i)] += 1
    my = defaultdict(int)
    for i in range(MAX,2*MAX):
        my[uf.root(i)] += 1
    ans = 0
    for i in range(2 * MAX):
        ans += mx[i] * my[i]
    print(ans - N)


def __starting_point():
    # ABC120_D()
    # ABC126_E()
    ABC131_F()
__starting_point()

import sys
from collections import defaultdict, deque
readline = sys.stdin.readline
N = int(readline())
P = defaultdict(list); Q = defaultdict(list)
for i in range(N):
    x, y = map(int, readline().split())
    P[x].append(y)
    Q[y].append(x)
X = set(); Y = set()

res = 0
que = deque()
for x in P:
    que.append((0, x))
    X.add(x)
    cx = 1; cy = 0
    while que:
        t, v = que.popleft()
        if t:
            for x in Q[v]:
                if x in X:
                    continue
                X.add(x)
                que.append((0, x))
                cx += 1
        else:
            for y in P[v]:
                if y in Y:
                    continue
                Y.add(y)
                que.append((1, y))
                cy += 1
    res += cx * cy
print(res - N)

from collections import Counter


class UnionFind:
    def __init__(self, size):
        self.data = [-1] * size

    def find(self, x):
        if self.data[x] < 0:
            return x
        else:
            self.data[x] = self.find(self.data[x])
            return self.data[x]

    def union(self, x, y):
        x, y = self.find(x), self.find(y)
        if x != y:
            if self.data[y] < self.data[x]:
                x, y = y, x
            self.data[x] += self.data[y]
            self.data[y] = x
        return (x != y)

    def same(self, x, y):
        return (self.find(x) == self.find(y))

    def size(self, x):
        return -self.data[self.find(x)]


def main():
    MAX = 10 ** 5 + 1
    uf = UnionFind(2 * MAX)

    N, *XY = list(map(int, open(0).read().split()))

    for x, y in zip(*[iter(XY)] * 2):
        uf.union(x, y + MAX)

    X = Counter(uf.find(i) for i in range(MAX))
    Y = Counter(uf.find(i) for i in range(MAX, MAX * 2))
    res = sum(X[i] * Y[i] for i in range(MAX * 2))

    print((res - N))


def __starting_point():
    main()

__starting_point()

import sys
from bisect import bisect_left

sr = lambda: sys.stdin.readline().rstrip()
ir = lambda: int(sr())
lr = lambda: list(map(int, sr().split()))

N = ir()
XY = [lr() for _ in range(N)]
V = list(range(2 * 10**5 + 1)) # 1-indexed X coordinate、Y coordinateの順番

# Aでxの根を求める
def find(A,x):
    p = A[x]
    if p == x: return x
    a = find(A,p)
    A[x] = a
    return a
# Aでxとyの属する集合の併合
def union(A, x, y):
    if find(A,x) > find(A,y):
        bx, by = find(A,y), find(A,x)
    else:
        bx, by = find(A,x), find(A,y)
    A[by] = bx # 根をbxに統一

for x, y in XY:
    y += 10**5
    if find(V, x) != find(V, y):
        union(V, x, y)

set_V = [set() for _ in range(2 * 10**5 + 1)] # 1-indexed
for x, y in XY:
    root = find(V, x)
    set_V[root] |= set([x, y+10**5])

set_V = [x for x in set_V if x]
answer = 0
for z in set_V:
    z = sorted(list(z))
    cnt_x = bisect_left(z, 10 ** 5 + 1)
    cnt_y = len(z) - cnt_x
    answer += cnt_x * cnt_y

answer -= N
print(answer)
# 48


import math,string,itertools,fractions,heapq,collections,re,array,bisect,sys,copy,functools

sys.setrecursionlimit(10**7)
inf = 10**20
eps = 1.0 / 10**10
mod = 998244353
dd = [(-1,0),(0,1),(1,0),(0,-1)]
ddn = [(-1,0),(-1,1),(0,1),(1,1),(1,0),(1,-1),(0,-1),(-1,-1)]

def LI(): return list(map(int, sys.stdin.readline().split()))
def LLI(): return [list(map(int, l.split())) for l in sys.stdin.readlines()]
def LI_(): return [int(x)-1 for x in sys.stdin.readline().split()]
def LF(): return [float(x) for x in sys.stdin.readline().split()]
def LS(): return sys.stdin.readline().split()
def I(): return int(sys.stdin.readline())
def F(): return float(sys.stdin.readline())
def S(): return input()
def pf(s): return print(s, flush=True)
def pe(s): return print(str(s), file=sys.stderr)
def JA(a, sep): return sep.join(map(str, a))
def JAA(a, s, t): return s.join(t.join(map(str, b)) for b in a)


class UnionFind:
    def __init__(self, size):
        self.table = [-1 for _ in range(size)]

    def find(self, x):
        if self.table[x] < 0:
            return x
        else:
            self.table[x] = self.find(self.table[x])
            return self.table[x]

    def union(self, x, y):
        s1 = self.find(x)
        s2 = self.find(y)
        if s1 != s2:
            if self.table[s1] <= self.table[s2]:
                self.table[s1] += self.table[s2]
                self.table[s2] = s1
            else:
                self.table[s2] += self.table[s1]
                self.table[s1] = s2
            return True
        return False

    def subsetall(self):
        a = []
        for i in range(len(self.table)):
            if self.table[i] < 0:
                a.append((i, -self.table[i]))
        return a

def main():
    n = I()
    xy = [LI() for _ in range(n)]
    xd = {}
    yd = {}

    xi = 0
    yi = 0
    for x,y in xy:
        if x not in xd:
            xd[x] = xi
            xi += 1
        if y not in yd:
            yd[y] = yi
            yi += 1

    uf = UnionFind(xi+yi+n)
    ii = xi + yi
    for i in range(n):
        x,y = xy[i]
        uf.union(xd[x], ii + i)
        uf.union(xi + yd[y], ii + i)

    dx = collections.defaultdict(set)
    dy = collections.defaultdict(set)
    for i in range(n):
        x,y = xy[i]
        ui = uf.find(ii + i)
        dx[ui].add(x)
        dy[ui].add(y)

    r = -n
    for k,xv in dx.items():
        yv = dy[k]
        r += len(xv) * len(yv)

    return r


print(main())



import sys
sys.setrecursionlimit(10000000)
MOD = 10 ** 9 + 7
INF = 10 ** 15

def main():
    MAXS = 100005
    N = int(input())
    X = [set() for _ in range(MAXS)]
    Y = [set() for _ in range(MAXS)]
    for _ in range(N):
        x,y = map(int,input().split())
        X[x].add(y)
        Y[y].add(x)
    
    used_x = [0] * MAXS
    used_y = [0] * MAXS
    ans = -N
    for i in range(MAXS):
        if used_x[i]:
            continue
        used_x[i] = 1
        if not X[i]:
            continue
        max_height = set(X[i])
        max_width = {i}
        stack = [(i,0)]
        while stack:
            v,typ = stack.pop()
            if typ == 0:
                for y in X[v]:
                    if used_y[y]:
                        continue
                    used_y[y] = 1
                    if Y[y]:
                        max_width |= Y[y]
                        stack.append((y,1))
            else:
                for x in Y[v]:
                    if used_x[x]:
                        continue
                    used_x[x] = 1
                    if X[x]:
                        max_height |= X[x]
                        stack.append((x,0))
        ans += len(max_width) * len(max_height)
    print(ans)
def __starting_point():
    main()
__starting_point()

from operator import itemgetter
from collections import defaultdict
import sys
input = sys.stdin.readline

# Union-Findデータ構造
class UnionFind:
    def __init__(self, numV):
        self.pars = list(range(numV))
        self.ranks = [0] * numV
    def find(self, x):
        if self.pars[x] == x: return x
        else:
            self.pars[x] = self.find(self.pars[x])
            return self.pars[x]
    def union(self, x, y):
        x, y = self.find(x), self.find(y)
        if x == y: return
        if self.ranks[x] < self.ranks[y]:
            self.pars[x] = y
        else:
            self.pars[y] = x
            if self.ranks[x] == self.ranks[y]:
                self.ranks[x] += 1
    def same(self, x, y):
        return self.find(x) == self.find(y)

N = int(input())
xys = []
for i in range(N):
    x, y = list(map(int, input().split()))
    xys.append((x, y, i))

UF = UnionFind(N)
for k in range(2):
    xys.sort(key=itemgetter(k))
    for i in range(N-1):
        if xys[i][k] == xys[i+1][k]:
            UF.union(xys[i][2], xys[i+1][2])

Xs = defaultdict(set)
Ys = defaultdict(set)
for x, y, i in xys:
    no = UF.find(i)
    Xs[no].add(x)
    Ys[no].add(y)

ans = 0
for no in list(Xs.keys()):
    ans += len(Xs[no]) * len(Ys[no])

print((ans - N))


import sys
read = sys.stdin.buffer.read
readlines = sys.stdin.buffer.readlines
input = sys.stdin.buffer.readline
sys.setrecursionlimit(10**7)


def main():
    OFS = 100005
    N = int(input())
    G = [[] for _ in [0]*OFS*2]
    for line in readlines():
        x, y = map(int, line.split())
        y += OFS
        G[x].append(y)
        G[y].append(x)

    def dfs(v):
        if v < OFS:
            counter[0] += 1
        else:
            counter[1] += 1

        for u in G[v]:
            if visited[u]:
                continue
            visited[u] = 1
            dfs(u)

    visited = [0]*OFS*2
    ans = 0
    for i in range(OFS):
        if visited[i]:
            continue
        counter = [0]*2
        visited[i] = 1
        dfs(i)
        ans += counter[0] * counter[1]

    print(ans - N)


def __starting_point():
    main()
__starting_point()

import sys
# input = sys.stdin.readline
sys.setrecursionlimit(pow(10, 6))

xedges = [[] for _ in range(100001)]
yedges = [[] for _ in range(100001)]
xvisited = [False for _ in range(100001)]
yvisited = [False for _ in range(100001)]

def dfs(i, xy):
  if xy == 0:
    xvisited[i] = True
  else:
    yvisited[i] = True
  xc, yc, ec = (xy+1)%2, xy%2, 0
  if xy == 0:
    ec += len(xedges[i])
    for toy in xedges[i]:
      if not yvisited[toy]:
        _xc, _yc, _ec = dfs(toy, 1)
        xc += _xc
        yc += _yc
        ec += _ec
  else :
    ec += len(yedges[i])
    for tox in yedges[i]:
      if not xvisited[tox]:
        _xc, _yc, _ec = dfs(tox, 0)
        xc += _xc
        yc += _yc
        ec += _ec
  return xc, yc, ec

def main():
  n = int(input())
  for _ in range(n):
    x, y = list(map(int, input().split()))
    xedges[x].append(y)
    yedges[y].append(x)

  ans = 0
  for i in range(1, 100001):
    if not xvisited[i] and len(xedges[i]) != 0:
      xc, yc, ec = dfs(i, 0)
      ans += xc*yc-ec//2
    elif not yvisited[i] and len(yedges[i]) != 0:
      xc, yc, ec = dfs(i, 1)
      ans += xc*yc-ec//2

  print(ans)


def __starting_point():
  main()

__starting_point()

N = int(input())
points = []
for i in range(0, N):
    points.append(list(map(int, input().split(' '))))
X = dict()
Y = dict()
count = 0
for p in points:
    if p[0] not in X.keys():
        X[p[0]] = []
    X[p[0]].append(p[1])
    if p[1] not in Y.keys():
        Y[p[1]] = []
    Y[p[1]].append(p[0])
while len(X.items()) > 0:
    item = list(X.items())[0]
    del X[item[0]]
    queue = [item]
    point_dict = {'x': [], 'y': []}
    queue_is_x = [True]
    while len(queue) > 0:
        item = queue.pop(0)
        is_x = queue_is_x.pop(0)
        if is_x:
            point_dict['x'].append(item[0])
            for i in item[1]:
                if i in Y.keys():
                    queue.append((i, Y[i]))
                    queue_is_x.append(False)
                    del Y[i]
        else:
            point_dict['y'].append(item[0])
            for i in item[1]:
                if i in X.keys():
                    queue.append((i, X[i]))
                    queue_is_x.append(True)
                    del X[i]
    # print((point_dict['x']))
    # print(len((point_dict['x'])))
    # print((point_dict['y']))
    # print(len((point_dict['y'])))
    count += len(set(point_dict['x'])) * len(set(point_dict['y']))

print(count - len(points))

import sys
sys.setrecursionlimit(10**6)
n = int(input())
v = 10**5+2
e = [[] for i in range(2*v)]
for i in range(n):
    x,y = list(map(int,input().split()))
    y += v
    e[x].append(y)
    e[y].append(x)
visit = [0]*(2*v)
def dfs(x):
    if not visit[x]:
        visit[x] = 1
        count[x//v] += 1
        for nex in e[x]:
            dfs(nex)
ans = 0
for i in range(v):
    if not visit[i]:
        count = [0]*2
        dfs(i)
        ans += count[0]*count[1]
print((ans-n))
    


class UnionFind :
    def __init__(self, size) :
        self.parent = list(range(size))
        self.height = [0] * size
        self.size = [1] * size
        self.component = size

    def root(self, index) :
        if self.parent[index] == index :  # 根の場合
            return index
        rootIndex = self.root(self.parent[index])  # 葉の場合親の根を取得
        self.parent[index] = rootIndex  # 親の付け直し
        return rootIndex

    def union(self, index1, index2) :  # 結合
        root1 = self.root(index1)
        root2 = self.root(index2)

        if root1 == root2 :  # 連結されている場合
            return

        self.component -= 1  # 連結成分を減らす

        if self.height[root1] < self.height[root2] :
            self.parent[root1] = root2  # root2に結合
            self.size[root2] += self.size[root1]
        else :
            self.parent[root2] = root1  # root1に結合
            self.size[root1] += self.size[root2]
            if self.height[root1] == self.height[root2] :
                self.height[root1] += 1
        return

    def isSameRoot(self, index1, index2) :
        return self.root(index1) == self.root(index2)

    def sizeOfSameRoot(self, index) :
        return self.size[self.root(index)]

    def getComponent(self) :
        return self.component

N = int(input())
R = 10**5 + 10
IXY = []

for i in range(N):
    x, y = list(map(int, input().split()))
    IXY.append((i, x, y))

X = [[] for _ in range(R)]
Y = [[] for _ in range(R)]
for i, x, y in IXY:
    X[x].append(i)
    Y[y].append(i)

tree = UnionFind(N)

for x in X:
    for fr, to in zip(x, x[1:]):
        tree.union(fr, to)
for y in Y:
    for fr, to in zip(y, y[1:]):
        tree.union(fr, to)

G = [[0, set(), set()] for _ in range(N)]
for i, x, y in IXY:
    r = tree.root(i)
    G[r][0] += 1
    G[r][1].add(x)
    G[r][2].add(y)

ans = 0
for grp in G:
    if grp[0] < 3:
        continue
    x = len(grp[1])
    y = len(grp[2])
    A = x * y
    ans += max(0, A - grp[0])
print(ans)


import sys
from collections import defaultdict
import queue

n = int(sys.stdin.readline())

graph = defaultdict(list)

X, Y = [[] for i in range(n)], [[] for i in range(n)]
atX, atY = defaultdict(list), defaultdict(list)

for i in range(n):
	a, b = map(int, sys.stdin.readline().split())
	a -= 1; b-= 1
	X[i] = a; Y[i] = b
	atX[X[i]].append(i)
	atY[Y[i]].append(i)

visX = set(); visY = set()
found = [False]*n

ans = n*(-1)
for root in range(n):
	if found[root]: continue
	usedX, usedY = set(), set()

	que = queue.Queue()
	que.put(root)
	found[root] = True
	usedX.add(X[root])
	usedY.add(Y[root])

	while not que.empty():
		cur = que.get()
		toVisit = []
		if X[cur] not in visX:
			visX.add(X[cur])
			for ele in atX[X[cur]]:
				toVisit.append(ele)
		if Y[cur] not in visY:
			visY.add(Y[cur])
			for ele in atY[Y[cur]]:
				toVisit.append(ele)

		for ele in toVisit:
			if found[ele]: continue
			found[ele] = True
			usedX.add(X[ele])
			usedY.add(Y[ele])
			que.put(ele)
	ans += len(usedX)*len(usedY)
print(ans)

import copy
import collections
import sys
read = sys.stdin.read
NXY = [int(_) for _ in read().split()]
N = NXY[0]
X = NXY[1::2]
Y = NXY[2::2]
xi = {x: i for i, x in enumerate(set(X))}
yj = {y: j for j, y in enumerate(set(Y))}


class UnionFind():
    def __init__(self, size):
        self.dat = list(range(size))
        self.SIZE = [1] * size  # 0-indexed

    def find(self, x):
        dat = self.dat
        stack = [x]
        while True:
            y = stack[-1]
            if dat[y] != y:
                stack += [dat[y]]
            else:
                break
        for s in stack:
            dat[s] = y
        return y

    def unite(self, x, y):
        is_same = self.is_same
        find = self.find
        SIZE = self.SIZE
        dat = self.dat
        if not is_same(x, y):
            X, Y = find(x), find(y)
            SX, SY = SIZE[X], SIZE[Y]
            if SX > SY:
                m = dat[X] = Y
            else:
                m = dat[Y] = X
            SIZE[m] = SX + SY
            SIZE[X + Y - m] = 0

    def is_same(self, x, y):
        find = self.find
        return find(x) == find(y)

    def scan_uf(self):
        find = self.find
        length = len(self.dat)
        for i in range(length):
            find(i)

    def size(self, x):
        return self.SIZE[self.find(x)]


ufx = UnionFind(len(xi))
ufy = UnionFind(len(yj))
sxy = collections.defaultdict(set)
syx = collections.defaultdict(set)
for x, y in zip(X, Y):
    i = xi[x]
    j = yj[y]
    sxy[i].add(j)
    syx[j].add(i)
sxy2 = copy.deepcopy(sxy)
for sy in list(sxy.values()):
    y1 = sy.pop()
    for y in sy:
        ufy.unite(y1, y)
for sx in list(syx.values()):
    x1 = sx.pop()
    for x in sx:
        ufx.unite(x1, x)
ufx.scan_uf()
ufy.scan_uf()
ans = 0
for x, vx in enumerate(ufx.SIZE):
    if vx:
        vy = ufy.size(sxy2[x].pop())
        ans += vx * vy
print((ans - N))


class Union_Find():
    def __init__(self, num):
        self.par = [-1]*(num+1)
        self.siz = [1]*(num+1)

    def same_checker(self, x, y):
        return self.find(x) == self.find(y)

    def find(self, x):
        if self.par[x] < 0:
            return x
        else:
            x = self.par[x]
            return self.find(x)

    def union(self, x, y):
        rx = self.find(x)
        ry = self.find(y)
        if rx != ry:
            if self.par[rx] < self.par[ry]:
                self.par[ry] = rx
                self.siz[rx] += self.siz[ry]
            elif self.par[rx] > self.par[ry]:
                self.par[rx] = ry
                self.siz[ry] += self.siz[rx]
            else:
                self.par[rx] -= 1
                self.par[ry] = rx
                self.siz[rx] += self.siz[ry]
        return

    def size(self, x):
        return self.siz[self.find(x)]

n = int(input())
uf = Union_Find(n)

d = [list(map(int, input().split())) for i in range(n)]
x = [[] for i in range(100001)]
y = [[] for i in range(100001)]

for i in range(n):
    xi, yi = d[i]
    x[xi].append(i+1)
    y[yi].append(i+1)


for i in range(1, 100001):
    for j in range(len(x[i])-1):
        uf.union(x[i][j], x[i][j+1])

for i in range(1, 100001):
    for j in range(len(y[i])-1):
        uf.union(y[i][j], y[i][j+1])

par = [[] for i in range(n+1)]
for i in range(1, n+1):
    par[uf.find(i)].append(d[i-1])

ans = -n
for i in range(1, n+1):
    x = set()
    y = set()
    for xi, yi in par[i]:
        x.add(xi)
        y.add(yi)

    ans += len(x)*len(y)

print(ans)


import sys
sys.setrecursionlimit(10**6)
def input(): return sys.stdin.readline().strip()

def main():

    """
    <UnionFindを使わない別解>

    x方向とy方向に分けて頂点を辺だと思うのは定石？？？（確かに長方形がメインなら直積分解したくなる？）
    このとき問題文は「長さ３のパスがあればパスを１本追加する操作を繰り返して、各連結成分を完全二部グラフにせよ」

    完全二部グラフになるのは背理法からすぐわかる。なので解法は(¥Sum_{i:component}X_i Y_i) - N
    """

    N = int(input())
    V = 10 ** 5 + 1 # padding for y
    repn = [[] for _ in range(V * 2)]
    for _ in range(N):
        x, y = list(map(int, input().split()))
        repn[x].append(V + y)
        repn[V + y].append(x)

    visited = [0] * (V * 2)
    count = [0, 0]

    # dfsでその都度連結成分を求める
    def dfs(v):
        if visited[v]:
            return
        visited[v] = 1
        count[v // V] += 1
        for u in repn[v]:
            dfs(u)

    ans = 0
    for i in range(V * 2):
        if visited[i]:
            continue
        count[:] = [0, 0]
        dfs(i)
        ans += count[0] * count[1]
    print((ans - N))


def __starting_point():
    main()

__starting_point()

from collections import Counter


def main():
    class UnionFind:
        def __init__(self, size):
            self.data = [-1] * size

        def find(self, x):
            if self.data[x] < 0:
                return x
            else:
                self.data[x] = self.find(self.data[x])
                return self.data[x]

        def union(self, x, y):
            x, y = self.find(x), self.find(y)
            if x != y:
                if self.data[y] < self.data[x]:
                    x, y = y, x
                self.data[x] += self.data[y]
                self.data[y] = x
            return (x != y)

    MAX = 10 ** 5 + 1
    uf = UnionFind(2 * MAX)

    N, *XY = list(map(int, open(0).read().split()))

    for x, y in zip(*[iter(XY)] * 2):
        uf.union(x, y + MAX)

    X = Counter(uf.find(i) for i in range(MAX))
    Y = Counter(uf.find(i) for i in range(MAX, MAX * 2))
    res = sum(X[i] * Y[i] for i in range(MAX * 2))

    print((res - N))


def __starting_point():
    main()

__starting_point()

from collections import Counter


def main():
    MAX = 10 ** 5 + 1

    data = [-1] * (2 * MAX)

    def find(x):
        if data[x] < 0:
            return x
        else:
            data[x] = find(data[x])
            return data[x]

    def union(x, y):
        x, y = find(x), find(y)
        if x != y:
            if data[y] < data[x]:
                x, y = y, x
            data[x] += data[y]
            data[y] = x
        return (x != y)


    N, *XY = map(int, open(0).read().split())

    for x, y in zip(*[iter(XY)] * 2):
        union(x, y + MAX)

    X = Counter(find(i) for i in range(MAX))
    Y = Counter(find(i) for i in range(MAX, MAX * 2))
    res = sum(X[i] * Y[i] for i in range(MAX * 2))

    print(res - N)


def __starting_point():
    main()
__starting_point()

# 二部ぐらふ、知らなかった
from collections import defaultdict


class UnionFind():
    def __init__(self, n):
        self.n = n
        self.parents = [-1] * (n+1)

    def find(self, x):
        if self.parents[x] < 0:
            return x
        else:
            self.parents[x] = self.find(self.parents[x])
            return self.parents[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)

        if x == y:
            return

        if self.parents[x] > self.parents[y]:
            x, y = y, x

        self.parents[x] += self.parents[y]
        self.parents[y] = x

    def size(self, x):
        return -self.parents[self.find(x)]

    def same(self, x, y):
        return self.find(x) == self.find(y)

    def members(self, x):
        root = self.find(x)
        return [i for i in range(self.n + 1) if self.find(i) == root]

    def roots(self):
        return [i for i, x in enumerate(self.parents) if x < 0]

    def group_count(self):
        return len(self.roots())

    def all_group_members(self):
        return {r: self.members(r) for r in self.roots()}

    def __str__(self):
        return '\n'.join('{}: {}'.format(r, self.members(r)) for r in self.roots())


N = int(input())
U = UnionFind(2*10**5)
for i in range(N):
    x, y = map(int, input().split())
    U.union(x, y+10**5)
ans = 0
Roots = U.roots()
D = {x: [] for x in Roots}
for i in range(2*10**5+1):
    D[U.find(i)].append(i)
for x in Roots:
    ss = D[x]
    sx = 0
    sy = 0
    for c in ss:
        if c <= 10**5:
            sx += 1
        else:
            sy += 1
    ans += sx*sy
ans -= N
print(ans)

#  --*-coding:utf-8-*--

def dsMakeSet(ds, x):
    ds[x] = x


def dsFind(ds, x):
    if ds[x] == x:
        return x

    x0 = x
    while ds[x] != x:
        x = ds[x]

    ds[x0] = x
    return x


def dsUnion(ds, x, y):
    xRoot = dsFind(ds, x)
    yRoot = dsFind(ds, y)
    
    if xRoot != yRoot:
        ds[yRoot] = xRoot
        return True

    return False



N = int(input())
Q = {}

for _ in range(N):
    x, y = list(map(int, input().split()))

    X = ('x', x)
    Y = ('y', y)

    if not (X in Q):
        dsMakeSet(Q, X)

    if not (Y in Q):
        dsMakeSet(Q, ('y', y))
    
    dsUnion(Q, X, Y)

R = {}

for q in Q:
    root = dsFind(Q, q)
    
    if not(root in R):
        R[root] = [0, 0]

    r = R[root]
    if q[0] == 'x':
        r[0] += 1
    else:
        r[1] += 1

s = 0
for r in list(R.values()):
    s += r[0]*r[1]

print((s - N))

    



import sys
sys.setrecursionlimit(10000000)

V = 100005
N = int(input())

lines = [[] for _ in range(V * 2)]
visited = [False] * V * 2

count = []
def dfs(v):
	nonlocal count
	if visited[v]:
		return
	visited[v] = True
	count[v // V] += 1
	for u in lines[v]:
		dfs(u)


for i in range(N):
	x, y = [int(x) for x in input().split()]
	y += V
	lines[x].append(y)
	lines[y].append(x)

ans = 0
for i in range(V * 2):
	if visited[i]:
		continue
	count = [0, 0]
	dfs(i)
	ans += count[0] * count[1]

ans -= N
print(ans)

n=int(input())
xy=[]
X=[]
Y=[]
for _ in range(n):
  x,y=list(map(int,input().split()))
  xy.append([x,y])
  X.append(x)
  Y.append(y)
X=list(set(X))
Y=list(set(Y))
X.sort()
Y.sort()
from bisect import bisect_left
xy=[[bisect_left(X,x),bisect_left(Y,y)] for x,y in xy]
mx=0
my=0
for x,y in xy:
  mx=max(mx,x)
  my=max(my,y)
mx,my=mx+1,my+1
bg=[[] for _ in range(mx+my)]
for x,y in xy:
  y+=mx
  bg[x].append(y)
  bg[y].append(x)
mi=set(range(mx+my))
ren=[]
while mi:
  todo=[mi.pop()]
  rx,ry=0,0
  while todo:
    v=todo.pop()
    if v<mx:
      rx+=1
    else:
      ry+=1
    for nv in bg[v]:
      if nv in mi:
        todo.append(nv)
        mi.discard(nv)
  ren.append([rx,ry])
ans=0
for rx,ry in ren:
  ans+=rx*ry
ans-=n
print(ans)


def main():
    from collections import deque
    import sys
    input = sys.stdin.readline

    M = 10 ** 5

    N = int(input())

    g = tuple(set() for _ in range(M * 2))
    for _ in range(N):
        x, y = (int(x) - 1 for x in input().split())  # 一律に移動
        g[x].add(y + M)
        g[y + M].add(x)

    visited = [0] * (M * 2)
    ans = 0
    for s in range(M * 2):  # xとy+Mのsetの方が確認する数は減る?
        if visited[s]: continue
        visited[s] = 1
        dq = deque()
        dq.append(s)

        sz = 0
        x = 0
        while dq:
            v = dq.popleft()
            sz += 1
            if v < M:
                x += 1
            for u in g[v]:
                if visited[u]: continue
                visited[u] = 1
                dq.append(u)
        ans += (sz - x) * x

    ans -= N

    print(ans)


def __starting_point():
    main()

# 連結成分ごとに
# x,yの二部グラフを完全二部グラフにする
# cnt(x)*cnt(y)-既存の辺数の総和

# 最後にN引けばよい

__starting_point()

import sys
sys.setrecursionlimit(10**5)
m = 100005
visited = [False]*(m*2)
cnt = [0,0]
def dfs(i):
    visited[i] = True
    cnt[i//m] = cnt[i//m]+1
    for j in to[i]:
        if not visited[j]:
            dfs(j)
 
input = sys.stdin.readline
n = int(input())
to = [[] for _ in range(m*2)]
for _ in range(n):
    x,y = map(int,input().split())
    y = y+m
    to[x].append(y)
    to[y].append(x)
 
ans = 0
for i in range(m):
    if not visited[i]:
        cnt = [0,0]
        dfs(i)
        ans = ans + cnt[0]*cnt[1]
 
print(ans-n)

def get_group(x):
    g = group[x]
    if g == x:
        return x
    group[g] = re = get_group(g)
    return re

def unite(x, y):
    gx = get_group(x)
    gy = get_group(y)
    if gx != gy:
        dx = depth[gx]
        dy = depth[gy]
        if dy > dx:
            gx, gy = gy, gx
        group[gy] = gx
        if dx == dy:
            depth[gx] += 1

n = int(input())
group = {}
depth = {}
edge = []
for _ in range(n):
    x, y = list(map(int, input().split()))
    group.setdefault(x, x)
    group.setdefault(-y, -y)
    depth.setdefault(x, 1)
    depth.setdefault(-y, 1)
    edge += [[x, -y]]

for x, y in edge:
    unite(x, y)

cnt = {}
for k in list(group.keys()):
    g = get_group(k)
    cnt.setdefault(g, [0, 0])
    if k > 0:
        cnt[g][0] += 1
    else:
        cnt[g][1] += 1

ans = 0
for cx, cy in list(cnt.values()):
    ans += cx * cy
print((ans - n))


class UnionFind:
    def __init__(self, n):
        self.p = [i for i in range(n)]
        self.components = n  # 連結成分の数

    def root(self, x):
        if self.p[x] == x:
            return x
        else:
            self.p[x] = self.root(self.p[x])
            return self.p[x]
    
    def unite(self, x, y):
        x, y = self.root(x), self.root(y)
        if x != y:
            self.p[x] = y
            self.components -= 1
    
    def same(self, x,y):
        return (self.root(x) == self.root(y))

N = int(input())
MAX = 100010
UF = UnionFind(2*MAX)

for _ in range(N):
    x, y = map(int,input().split())
    UF.unite(x,y+MAX)


cnt_x, cnt_y = [0]*(2*MAX), [0]*(2*MAX)

for i in range(MAX):
    cnt_x[UF.root(i)] += 1

for i in range(MAX,2*MAX):
    cnt_y[UF.root(i)] += 1

print(sum([cnt_x[i]*cnt_y[i] for i in range(2*MAX)]) - N)

import numpy as np
from scipy.sparse import csr_matrix
from scipy.sparse.csgraph import connected_components
n=int(input())
m=10**5+5
l=[]
lx=[]
for _ in range(n):
    a,b=map(int,input().split())
    l.append((a,b+m,1))
    lx.append(a)
edge=np.array(l,dtype=np.int64).T
graph=csr_matrix(((edge[2],edge[:2]-1)),(2*m,2*m))
n,labels=connected_components(graph)
lx=np.array(lx)
x=np.bincount(labels[:m],minlength=n)
y=np.bincount(labels[m:],minlength=n)
e=np.bincount(labels[lx],minlength=n)
print((x*y-e).sum())

# using main() makes code faster from the point of view of "access to variables in nonlocal name-space"
# def main():

# for i, a in enumerate(iterable)
# q, mod = divmod(a, b)
# divmod(x, y) returns the tuple (x//y, x%y)
# manage median(s) using two heapq https://atcoder.jp/contests/abc127/tasks/abc127_f
import sys
sys.setrecursionlimit(10**7)
from itertools import accumulate, combinations, permutations # https://docs.python.org/ja/3/library/itertools.html
from math import factorial
def factorize(n):
    """return the factors of the Arg and count of each factor
    
    Args:
        n (long): number to be resolved into factors
    
    Returns:
        list of tuples: factorize(220) returns [(2, 2), (5, 1), (11, 1)]
    """
    fct = []  # prime factor
    b, e = 2, 0  # base, exponent
    while b * b <= n:
        while n % b == 0:
            n = n // b
            e = e + 1
        if e > 0:
            fct.append((b, e))
        b, e = b + 1, 0
    if n > 1:
        fct.append((n, 1))
    return fct
def combinations_count(n, r):
    """Return the number of selecting r pieces of items from n kinds of items.
    
    Args:
        n (long): number
        r (long): number
    
    Raises:
        Exception: not defined when n or r is negative
    
    Returns:
        long: number
    """
    # TODO: How should I do when n - r is negative?
    if n < 0 or r < 0:
        raise Exception('combinations_count(n, r) not defined when n or r is negative')
    if n - r < r: r = n - r
    if r < 0: return 0
    if r == 0: return 1
    if r == 1: return n
    numerator = [n - r + k + 1 for k in range(r)]
    denominator = [k + 1 for k in range(r)]
    for p in range(2,r+1):
        pivot = denominator[p - 1]
        if pivot > 1:
            offset = (n - r) % p
            for k in range(p-1,r,p):
                numerator[k - offset] /= pivot
                denominator[k] /= pivot
    result = 1
    for k in range(r):
        if numerator[k] > 1:
            result *= int(numerator[k])
    return result
def combinations_with_replacement_count(n, r):
    """Return the number of selecting r pieces of items from n kinds of items allowing individual elements to be repeated more than once.
    
    Args:
        n (long): number
        r (long): number
    
    Raises:
        Exception: not defined when n or r is negative
    
    Returns:
        long: number
    """
    if n < 0 or r < 0:
        raise Exception('combinations_with_replacement_count(n, r) not defined when n or r is negative')
    elif n == 0:
        return 1
    else:
        return combinations_count(n + r - 1, r)
from collections import deque, Counter, defaultdict # https://docs.python.org/ja/3/library/collections.html#collections.deque
from heapq import heapify, heappop, heappush, heappushpop, heapreplace,nlargest,nsmallest # https://docs.python.org/ja/3/library/heapq.html
from copy import deepcopy, copy # https://docs.python.org/ja/3/library/copy.html
from operator import itemgetter
# ex1: List.sort(key=itemgetter(1))
# ex2: sorted(tuples, key=itemgetter(1,2))
from functools import reduce
from fractions import gcd # Deprecated since version 3.5: Use math.gcd() instead.
def gcds(numbers):
    return reduce(gcd, numbers)
def lcm(x, y):
    return (x * y) // gcd(x, y)
def lcms(numbers):
    return reduce(lcm, numbers, 1)

# first create factorial_list
# fac_list = mod_factorial_list(n)
INF = 10 ** 18
MOD = 10 ** 9 + 7
modpow = lambda a, n, p = MOD: pow(a, n, p) # Recursive function in python is slow!
def modinv(a, p = MOD):
    # evaluate reciprocal using Fermat's little theorem:
    # a**(p-1) is identical to 1 (mod p) when a and p is coprime
    return modpow(a, p-2, p)
def modinv_list(n, p = MOD):
    if n <= 1:
        return [0,1][:n+1]
    else:
        inv_t = [0,1]
        for i in range(2, n+1):
            inv_t += [inv_t[p % i] * (p - int(p / i)) % p]
        return inv_t
def modfactorial_list(n, p = MOD):
    if n == 0:
        return [1]
    else:
        l = [0] * (n+1)
        tmp = 1
        for i in range(1, n+1):
            tmp = tmp * i % p
            l[i] = tmp
        return l
def modcomb(n, k, fac_list = [], p = MOD):
    # fac_list = modfactorial_list(100)
    # print(modcomb(100, 5, modfactorial_list(100)))
    from math import factorial
    if n < 0 or k < 0 or n < k: return 0
    if n == 0 or k == 0: return 1
    if len(fac_list) <= n:
        a = factorial(n) % p
        b = factorial(k) % p
        c = factorial(n-k) % p
    else:
        a = fac_list[n]
        b = fac_list[k]
        c = fac_list[n-k]
    return (a * modpow(b, p-2, p) * modpow(c, p-2, p)) % p
def modadd(a, b, p = MOD):
    return (a + b) % MOD
def modsub(a, b, p = MOD):
    return (a - b) % p
def modmul(a, b, p = MOD):
    return ((a % p) * (b % p)) % p
def moddiv(a, b, p = MOD):
    return modmul(a, modpow(b, p-2, p))

# initialize variables and set inputs
# initialize variables
    # to initialize list, use [0] * n
    # to initialize two dimentional array, use [[0] * N for _ in range(N)]
# set inputs
    # open(0).read() is a convenient method:
    # ex) n, m, *x = map(int, open(0).read().split())
    #     min(x[::2]) - max(x[1::2])
    # ex2) *x, = map(int, open(0).read().split())
    #     don't forget to add comma after *x if only one variable is used
# preprocessing
    # transpose = [x for x in zip(*data)]
    # ex) [[1, 2, 3], [4, 5, 6], [7, 8, 9]] => [(1, 4, 7), (2, 5, 8), (3, 6, 9)]
# calculate and output
    # output pattern
    # ex1) print(*l) => when l = [2, 5, 6], printed 2 5 6

# functions used
r = lambda: sys.stdin.readline().strip()
r_int = lambda: int(r())
R = lambda: list(map(int, r().split()))
Rfloat = lambda: list(map(float, r().split()))
Rtuple = lambda: tuple(map(int, r().split()))
Rmap = lambda: list(map(int, r().split()))
# single int: int(r())
# single string: r()
# single float: float(r())
# line int: R()
# line string: r().split()
# line (str, int, int): [j if i == 0 else int(j) for i, j in enumerate(r().split())]
# lines int: [R() for _ in range(n)]

# set inputs
# sys.stdin = open('sample.txt') # for test
N = r_int()
Dic = defaultdict(list)
V = 10**5 + 5
Arrived = [False] * (2 * V)
for _ in range(N):
    x, y = R()
    y += V
    Dic[x].append(y)
    Dic[y].append(x)

ans = 0
task = []
for k, v in list(Dic.items()):
    if Arrived[k]:
        continue
    Arrived[k] = True
    cntx = 0
    cnty = 0
    if k < V:
        cntx += 1
    else:
        cnty += 1
    cntdot = 0
    task.extend(v)
    cntdot += len(v)
    while task:
        s = task.pop()
        if Arrived[s]:
            continue
        if s < V:
            cntx += 1
        else:
            cnty += 1
        Arrived[s] = True
        task.extend(Dic[s])
        cntdot += len(Dic[s])
    ans += cntx * cnty - cntdot // 2
print(ans)

# def __starting_point():
#     main()

__starting_point()

class UnionFind:
    def __init__(self, n):
        self.v = [-1 for _ in range(n)]

    def find(self, x):
        if self.v[x] < 0:  # (負)は根
            return x
        else:
            self.v[x] = self.find(self.v[x])
            return self.v[x]

    def unite(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if -self.v[x] < -self.v[y]:
            x, y = y, x
        self.v[x] += self.v[y]
        self.v[y] = x

    def root(self, x):
        return self.v[x] < 0

    def size(self, x):
        return -self.v[self.find(x)]


mx = 10 ** 5 + 10

n = int(input())

uf = UnionFind(mx * 2)

for _ in range(n):
    x, y = list(map(int, input().split()))
    uf.unite(x, y + mx)

vx = [0] * (mx * 2)
vy = [0] * (mx * 2)

for i in range(mx):
    root = uf.find(i)
    vx[root] += 1

for i in range(mx, mx * 2):
    root = uf.find(i)
    vy[root] += 1

ans = -n
for xx, yy in zip(vx, vy):
    ans += xx * yy
print(ans)


import sys
sys.setrecursionlimit(10**5)
v = 100005
visited = [False]*(2*v)
count = [0, 0]

def dfs(a):
    visited[a] = True
    count[a//v] += 1
    for i in vertex[a]:
        if not visited[i]:
            dfs(i)

input = sys.stdin.readline
N = int(input())
vertex = [[] for _ in range(v*2)]
for _ in range(N):
    x, y=map(int, input().split())
    y += v
    vertex[x].append(y)
    vertex[y].append(x)

ans = 0
for i in range(v):
    if not visited[i]:
        count = [0, 0]
        dfs(i)
        ans += count[0]*count[1]

print(ans-N)

import sys
sys.setrecursionlimit(100000)

n = int(input())
xy = [list(map(int, input().split())) for _ in range(n)]
xtoy = {}
ytox = {}
for x, y in xy:
    if x not in xtoy:
        xtoy[x] = [y]
    else:
        xtoy[x].append(y)
    if y not in ytox:
        ytox[y] = [x]
    else:
        ytox[y].append(x)

def doit(x, xs, ys, xtoy, ytox):
    ret = 0
    if x in xtoy:
        xs.add(x)
        yl = xtoy.pop(x)
        ret += len(yl)
        for y in yl:
            ret += doit(y, ys, xs, ytox, xtoy)
    return ret

ret = 0
while len(xtoy) > 0:
    x = next(iter(xtoy))
    xs = set()
    ys = set()
    num = doit(x, xs, ys, xtoy, ytox)
    ret += len(xs) * len(ys) - num // 2

print(ret)

class UnionFind():
    def __init__(self,size):
        self.table=[-1]*size
    def root(self,x):
        while self.table[x]>=0:
            x=self.table[x]
        return x
    def unite(self,x,y):
        s1=self.root(x)
        s2=self.root(y)
        if s1!=s2:
            if self.table[s1]>self.table[s2]:
                s1,s2=s2,s1
            self.table[s1]+=self.table[s2]
            self.table[s2]=s1
        return
    def same(self,x,y):
        return self.root(x)==self.root(y)
    def size(self,x):
        return -self.table[self.root(x)]
n,*z=map(int,open(0).read().split())
u=UnionFind(10**5*2+1)
for x,y in zip(*[iter(z)]*2):
    u.unite(x,10**5+y)
d=[[0,set(),set()]for _ in range(10**5*2+1)]
for x,y in zip(*[iter(z)]*2):
    t=d[u.root(x)]
    t[0]+=1
    t[1].add(x)
    t[2].add(y)
print(sum(len(x)*len(y)-a for a,x,y in d))