AtCoder Beginner Contest 131 - Must Be Rectangular!

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.

Source: atcoder | Difficulty: MEDIUM


Solutions:
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)


import sys
from collections import defaultdict
sys.setrecursionlimit(10 ** 9)
INF = 10 ** 18
MOD = 10 ** 9 + 7
input = lambda : sys.stdin.readline().rstrip()
YesNo = lambda b: bool([print('Yes')] if b else print('No'))
YESNO = lambda b: bool([print('YES')] if b else print('NO'))
int1 = lambda x: int(x) - 1
N = int(input())

class UnionFind:

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

	def root(self, x):
		while self.par[x] != x:
			self.par[x] = self.par[self.par[x]]
			x = self.par[x]
		return x

	def unite(self, x, y):
		x = self.root(x)
		y = self.root(y)
		if x == y:
			return False
		if self.siz[x] < self.siz[y]:
			(x, y) = (y, x)
		self.siz[x] += self.siz[y]
		self.par[y] = x
		return True

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

	def size(self, x):
		return self.siz[self.root(x)]
MAX = 10 ** 5
u = UnionFind(MAX * 2)
for _ in range(N):
	(x, y) = map(int1, input().split())
	u.unite(x, y + MAX)
(lx, ly) = ([0] * MAX * 2, [0] * MAX * 2)
for i in range(MAX):
	lx[u.root(i)] += 1
	ly[u.root(i + MAX)] += 1
ans = 0
for i in range(MAX * 2):
	ans += lx[i] * ly[i]
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())

def Find(x):
	global table
	if table[x] == x:
		return x
	else:
		table[x] = Find(table[x])
		size[x] = size[table[x]]
		return table[x]

def Unite(x, y):
	global size
	global rank
	x = Find(x)
	y = Find(y)
	sx = Size(x)
	sy = Size(y)
	if x == y:
		return
	if rank[x] > rank[y]:
		table[y] = x
		size[x] = sx + sy
	else:
		table[x] = y
		size[y] = sx + sy
		if rank[x] == rank[y]:
			rank[y] += 1

def Check(x, y):
	if Find(x) == Find(y):
		return True
	else:
		return False

def Size(x):
	return size[Find(x)]
table = [i for i in range(10 ** 5 + 1)]
rank = [1 for i in range(10 ** 5 + 1)]
size = [1 for i in range(10 ** 5 + 1)]
N = int(input())
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 Check(s, t):
			Unite(s, t)
ans = 0
for x in xx:
	s = tatedict[x][0]
	ans += Size(s) - len(tatedict[x])
print(ans)


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)


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)


from operator import itemgetter

class UnionFindTree:

	def __init__(self, N):
		self.__parent_of = [None] * N
		self.__rank_of = [0] * N
		self.__size = [1] * N

	def root(self, value):
		if self.__parent_of[value] is None:
			return value
		else:
			self.__parent_of[value] = self.root(self.__parent_of[value])
			return self.__parent_of[value]

	def unite(self, a, b):
		r1 = self.root(a)
		r2 = self.root(b)
		if r1 != r2:
			if self.__rank_of[r1] < self.__rank_of[r2]:
				self.__parent_of[r1] = r2
				self.__size[r2] += self.__size[r1]
			else:
				self.__parent_of[r2] = r1
				self.__size[r1] += self.__size[r2]
				if self.__rank_of[r1] == self.__rank_of[r2]:
					self.__rank_of[r1] += 1

	def is_same(self, a, b):
		return self.root(a) == self.root(b)

	def size(self, a):
		return self.__size[self.root(a)]

	def groups(self):
		groups = {}
		for k in range(len(self.__parent_of)):
			r = self.root(k)
			if r not in groups:
				groups[r] = []
			groups[r].append(k)
		return [groups[x] for x in groups]

def main():
	N = int(input())
	uft = UnionFindTree(N)
	P = [None] * N
	for i in range(N):
		(x, y) = map(int, input().split())
		P[i] = (i, x, y)
	Q = sorted(P, key=itemgetter(1))
	for i in range(1, N):
		if Q[i - 1][1] == Q[i][1]:
			uft.unite(Q[i - 1][0], Q[i][0])
	R = sorted(P, key=itemgetter(2))
	for i in range(1, N):
		if R[i - 1][2] == R[i][2]:
			uft.unite(R[i - 1][0], R[i][0])
	groups = uft.groups()
	ans = 0
	for g in groups:
		if len(g) < 3:
			continue
		xs = set()
		ys = set()
		for i in g:
			(_, x, y) = P[i]
			xs.add(x)
			ys.add(y)
		ans += len(xs) * len(ys) - len(g)
	print(ans)
main()


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)


n = int(input())
xy = []
X = []
Y = []
for _ in range(n):
	(x, y) = 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 uf_init(n):
	return list(range(n))

def uf_root(uf, x):
	if uf[x] != x:
		uf[x] = uf_root(uf, uf[x])
	return uf[x]

def uf_same(uf, x, y):
	return uf_root(uf, x) == uf_root(uf, y)

def uf_unite(uf, x, y):
	x = uf_root(uf, x)
	y = uf_root(uf, y)
	if x == y:
		return
	uf[x] = y
N = int(input())
x_dict = {}
y_dict = {}
uf = uf_init(N)
xy_arr = []
for i in range(N):
	(x, y) = (int(x) for x in input().split())
	if x in x_dict:
		uf_unite(uf, i, x_dict[x])
	else:
		x_dict[x] = i
	if y in y_dict:
		uf_unite(uf, i, y_dict[y])
	else:
		y_dict[y] = i
	xy_arr.append((x, y))
unite_dict = {}
for i in range(N):
	ur = uf_root(uf, i)
	unite_dict.setdefault(ur, [set(), set()])
	unite_dict[ur][0].add(xy_arr[i][0])
	unite_dict[ur][1].add(xy_arr[i][1])
total_points = 0
for (k, v) in unite_dict.items():
	total_points += len(v[0]) * len(v[1])
print(total_points - N)


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]
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) = 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)


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):
		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) 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())
xtoy = [-1] * (10 ** 5 + 1)
uf = UnionFind(10 ** 5 + 1)
xy = []
for i in range(N):
	(x, y) = map(int, input().split())
	if xtoy[x] == -1:
		xtoy[x] = y
	xy.append((x, y))
for (x, y) in xy:
	uf.union(y, xtoy[x])
ans = -N
for i in range(10 ** 5 + 1):
	if xtoy[i] != -1:
		ans += uf.size(xtoy[i])
print(ans)


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(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
from collections import deque
n = int(input())
xy = [list(map(int, input().split())) for _ in range(n)]
x_list = {}
y_list = {}
for i in range(1, 10 ** 5 + 1):
	x_list[i] = set()
	y_list[i] = set()
for i in range(n):
	(x, y) = xy[i]
	x_list[x].add(i)
	y_list[y].add(i)
remain = set(range(n))
ans = 0
while len(remain) > 0:
	d = deque()
	d.append(remain.pop())
	num = 0
	xs = set()
	ys = set()
	while len(d) > 0:
		tmp = d.pop()
		(x, y) = xy[tmp]
		num += 1
		xs.add(x)
		ys.add(y)
		while len(x_list[x]) > 0:
			j = x_list[x].pop()
			if j in remain:
				d.append(j)
				remain.remove(j)
		while len(y_list[y]) > 0:
			j = y_list[y].pop()
			if j in remain:
				d.append(j)
				remain.remove(j)
	ans += len(xs) * len(ys) - num
print(ans)


n = int(input())

class unionfind:

	def __init__(self, n):
		self.n = n
		self.root = [-1] * (n + 1)
		self.rnk = [0] * (n + 1)

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

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x == y:
			return
		elif self.rnk[x] > self.rnk[y]:
			self.root[x] += self.root[y]
			self.root[y] = x
		else:
			self.root[y] += self.root[x]
			self.root[x] = y
			if self.rnk[x] == self.rnk[y]:
				self.rnk[y] += 1

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

	def count(self, x):
		return -self.root[self.find(x)]
uf = unionfind(n)
import collections
dx = collections.defaultdict(int)
dy = collections.defaultdict(int)
l = [list(map(int, input().split())) for i in range(n)]
for (i, j) in enumerate(l, 1):
	(x, y) = j
	if dx[x]:
		uf.unite(dx[x], i)
	else:
		dx[x] = i
	if dy[y]:
		uf.unite(dy[y], i)
	else:
		dy[y] = i
dx = collections.defaultdict(set)
dy = collections.defaultdict(set)
for (i, j) in enumerate(l, 1):
	(x, y) = j
	dx[uf.find(i)].add(x)
	dy[uf.find(i)].add(y)
ans = 0
for (i, j) in dx.items():
	ans += len(j) * len(dy[i]) - uf.count(i)
print(ans)


N = int(input())
COR = [tuple(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 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)


def main():
	N = int(input())
	(X, Y) = ({}, {})
	for _ in range(N):
		(x, y) = 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, 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(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()


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()
__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):
	global 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)


from collections import deque
N = int(input())
x_list = [0] * N
y_list = [0] * N
for i in range(N):
	(x, y) = map(int, input().split())
	x_list[i] = x + 1000000
	y_list[i] = y
G = dict()
for i in range(N):
	x = x_list[i]
	y = y_list[i]
	G[x] = dict()
	G[y] = dict()
for i in range(N):
	x = x_list[i]
	y = y_list[i]
	G[x][y] = 1
	G[y][x] = 1
x_su = list(set(x_list))
Lx = len(x_su)
x_su_ind = dict()
for i in range(Lx):
	x_su_ind[x_su[i]] = i
y_su = list(set(y_list))
Ly = len(y_su)
y_su_ind = dict()
for i in range(Ly):
	y_su_ind[y_su[i]] = i
done_x = [0] * Lx
done_y = [0] * Ly
res = 0
id_0 = 0
while True:
	while done_x[id_0] == 1:
		id_0 += 1
		if id_0 == Lx:
			break
	if id_0 == Lx:
		break
	cx = 1
	cy = 0
	queue = deque([x_su[id_0]])
	done_x[id_0] = 1
	while len(queue) > 0:
		p = queue.popleft()
		for q in G[p].keys():
			if q < 1000000:
				if done_y[y_su_ind[q]] == 0:
					queue.append(q)
					done_y[y_su_ind[q]] = 1
					cy += 1
			elif done_x[x_su_ind[q]] == 0:
				queue.append(q)
				done_x[x_su_ind[q]] = 1
				cx += 1
	res += cx * cy
print(res - N)


from collections import defaultdict

def solve(points):
	N = len(points)
	X = defaultdict(list)
	Y = defaultdict(list)
	for (i, (x, y)) in enumerate(points):
		X[x].append(i)
		Y[y].append(i)
	res = 0
	visited = [False] * N
	for p0 in range(N):
		if visited[p0]:
			continue
		stack = [p0]
		visited[p0] = True
		xx = set()
		yy = set()
		while stack:
			p = stack.pop()
			(x, y) = points[p]
			xx.add(x)
			yy.add(y)
			for q in X[x]:
				if not visited[q]:
					visited[q] = True
					stack.append(q)
			for q in Y[y]:
				if not visited[q]:
					visited[q] = True
					stack.append(q)
		res += len(xx) * len(yy)
	return res - N
N = int(input())
points = [tuple(map(int, input().split())) for _ in range(N)]
print(solve(points))


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

def YesNo(x):
	return 'Yes' if x else 'No'

def LI():
	return [int(x) for x in sys.stdin.readline().split()]

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 II():
	return int(sys.stdin.readline())

def SI():
	return input()
from collections import defaultdict

def main():
	N = II()
	xy = []
	for _ in range(N):
		xy.append(LI())
	xy2g = defaultdict(int)
	g2xy = defaultdict(set)
	for (x, y) in xy:
		y = -y
		gx = xy2g[x]
		gy = xy2g[y]
		if gx != 0 and gy != 0 and (gx != gy):
			if len(g2xy[gx]) < len(g2xy[gy]):
				(gx, gy) = (gy, gx)
			for xy in g2xy[gy]:
				xy2g[xy] = gx
			g2xy[gx] |= g2xy[gy]
			g2xy[gy].clear()
		else:
			g = gx or gy or x
			xy2g[x] = xy2g[y] = g
			g2xy[g] |= {x, y}
	ans = -N
	for xys in g2xy.values():
		nx = sum((1 for xy in xys if xy > 0))
		ny = len(xys) - nx
		ans += nx * ny
	return ans
print(main())


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)


class UnionFind:

	def __init__(self, n):
		self.p = [i for i in range(n)]
		self.component = 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.component -= 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 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))
main()


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
			self.size[root2] += self.size[root1]
		else:
			self.parent[root2] = 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.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)


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)]
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 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)
main()


import sys
input = sys.stdin.readline
N = int(input())
s = set()
res = 0

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.root = [-1] * (n + 1)
		self.rnk = [0] * (n + 1)

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

	def Unite(self, x, y):
		x = self.Find_Root(x)
		y = self.Find_Root(y)
		if x == y:
			return
		elif self.rnk[x] > self.rnk[y]:
			self.root[x] += self.root[y]
			self.root[y] = x
		else:
			self.root[y] += self.root[x]
			self.root[x] = y
			if self.rnk[x] == self.rnk[y]:
				self.rnk[y] += 1

	def isSameGroup(self, x, y):
		return self.Find_Root(x) == self.Find_Root(y)

	def Count(self, x):
		return -self.root[self.Find_Root(x)]
uf = UnionFind(2 * 10 ** 5)
for _ in range(N):
	(u, v) = map(int, input().split())
	s.add(u)
	uf.Unite(u, v + 10 ** 5)
ss = set()
c = [0] * (2 * 10 ** 5 + 1)
for x in s:
	y = uf.Find_Root(x)
	ss.add(y)
	c[y] += 1
for x in ss:
	t = uf.Count(x)
	xc = c[x]
	yc = t - xc
	res += xc * yc
print(res - N)


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):
	global 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
input = sys.stdin.readline
from collections import *

def bfs(s):
	color[s] = 1
	q = deque([s])
	(b, w) = (1, 0)
	while q:
		v = q.popleft()
		for nv in G[v]:
			if color[nv] == 0:
				color[nv] = -color[v]
				q.append(nv)
				if color[nv] == 1:
					b += 1
				else:
					w += 1
	return b * w
N = int(input())
(xy, xs, ys) = ([], [], [])
for _ in range(N):
	(x, y) = map(int, input().split())
	xy.append((x, y))
	xs.append(x)
	ys.append(y)
xs = list(set(xs))
ys = list(set(ys))
xs.sort()
ys.sort()
idx1 = defaultdict(int)
for i in range(len(xs)):
	idx1[xs[i]] = i
idx2 = defaultdict(int)
for i in range(len(ys)):
	idx2[ys[i]] = i + len(xs)
M = len(xs) + len(ys)
G = [[] for _ in range(M)]
for (x, y) in xy:
	G[idx1[x]].append(idx2[y])
	G[idx2[y]].append(idx1[x])
color = [0] * M
S = 0
for i in range(M):
	if color[i] == 0:
		S += bfs(i)
print(S - N)


def main():
	from collections import defaultdict
	import sys
	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()


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())


import sys
input = sys.stdin.readline
sys.setrecursionlimit(1000000)
N = int(input())
K = 10 ** 5 + 1
P = [i for i in range(K)]

def par(a):
	if P[a] == a:
		return a
	t = par(P[a])
	P[a] = t
	return t

def cb(a, b):
	pa = par(a)
	pb = par(b)
	if pa != pb:
		P[pa] = pb
X = [[] for _ in range(K)]
Y = [[] for _ in range(K)]
for _ in range(N):
	(x, y) = map(int, input().split())
	X[x - 1].append(y - 1)
	Y[y - 1].append(x - 1)
for i in range(K):
	for j in range(len(Y[i]) - 1):
		cb(Y[i][j], Y[i][j + 1])
M = [[] for _ in range(K)]
for i in range(K):
	M[par(i)].append(i)
ans = -N
for i in range(K):
	S = []
	for j in M[i]:
		for k in X[j]:
			S.append(k)
	S = list(set(S))
	ans += len(S) * len(M[i])
print(ans)


def dfs(v):
	cnt = [0, 0]
	if v < OFFSET:
		cnt[0] += 1
	else:
		cnt[1] += 1
	visited[v] = True
	stack = [v]
	while stack:
		v = stack.pop()
		for nxt_v in graph[v]:
			if visited[nxt_v]:
				continue
			if nxt_v < OFFSET:
				cnt[0] += 1
			else:
				cnt[1] += 1
			visited[nxt_v] = True
			stack.append(nxt_v)
	return cnt[0] * cnt[1]
n = int(input())
info = [list(map(int, input().split())) for i in range(n)]
OFFSET = 10 ** 5
graph = [[] for i in range(2 * OFFSET)]
for (x, y) in info:
	x -= 1
	y -= 1
	y += OFFSET
	graph[x].append(y)
	graph[y].append(x)
ans = 0
visited = [False] * (2 * OFFSET)
for v in range(2 * OFFSET):
	if visited[v]:
		continue
	else:
		ans += dfs(v)
print(ans - n)


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:

	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()


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
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)
comp_y = np.bincount(comp[U:], minlength=Ncomp)
comp_E = np.bincount(comp[X], minlength=Ncomp)
answer = (comp_x * comp_y - comp_E).sum()
print(answer)


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)


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 = [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)
main()


N = int(input())
XY = [tuple(map(int, input().split())) for i in range(N)]
V = 10 ** 5 + 5

class UnionFind:

	def __init__(self, N):
		self.parent = [i for i in range(N)]
		self.rank = [0] * N
		self.size_x = [1 - i // V for i in range(N)]
		self.size_y = [i // V for i in range(N)]

	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.rank[ra] < self.rank[rb]:
			self.parent[ra] = rb
			self.size_x[rb] += self.size_x[ra]
			self.size_y[rb] += self.size_y[ra]
		else:
			self.parent[rb] = ra
			if self.rank[ra] == self.rank[rb]:
				self.rank[ra] += 1
			self.size_x[ra] += self.size_x[rb]
			self.size_y[ra] += self.size_y[rb]
uf = UnionFind(V * 2)
for (x, y) in XY:
	uf.unite(x, y + V)
for i in range(V * 2):
	uf.root(i)
ans = 0
used = set()
for i in range(V * 2):
	r = uf.root(i)
	if r in used:
		continue
	used.add(r)
	ans += uf.size_x[r] * uf.size_y[r]
ans -= N
print(ans)


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
			self.size[root2] += self.size[root1]
		else:
			self.parent[root2] = 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)


from itertools import *
from math import *
from collections import *
from heapq import *
from bisect import bisect_left, bisect_right
from copy import deepcopy
inf = float('inf')
mod = 10 ** 9 + 7
from functools import reduce
import sys
sys.setrecursionlimit(10 ** 7)

class UnionFind:

	def __init__(self, N):
		self.rank = [0] * N
		self.par = [i for i in range(N)]
		self.counter = [1] * 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 unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if x != y:
			z = self.counter[x] + self.counter[y]
			(self.counter[x], self.counter[y]) = (z, z)
		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 size(self, x):
		x = self.find(x)
		return self.counter[x]

	def same(self, x, y):
		return self.find(x) == self.find(y)
N = int(input())
uf = UnionFind(N)
x_dict = {}
y_dict = {}
xy = []
for i in range(N):
	(x, y) = map(int, input().split())
	if x in x_dict:
		uf.unite(i, x_dict[x])
	else:
		x_dict[x] = i
	if y in y_dict:
		uf.unite(i, y_dict[y])
	else:
		y_dict[y] = i
	xy.append((x, y))
unite_dict = {}
for i in range(N):
	ur = uf.find(i)
	unite_dict.setdefault(ur, [set(), set()])
	unite_dict[ur][0].add(xy[i][0])
	unite_dict[ur][1].add(xy[i][1])
p = 0
for (k, v) in unite_dict.items():
	p += len(v[0]) * len(v[1])
print(p - N)


class Uf:

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

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

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

	def unite(self, x, y):
		u = self.root(x)
		v = self.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 count(self, x):
		return self.size[self.root(x)]
from operator import itemgetter
from collections import defaultdict
from itertools import groupby
N = int(input())
XYI = []
for i in range(N):
	(x, y) = map(int, input().split())
	XYI.append([x, y, i])
XYx = sorted(XYI, key=itemgetter(0))
XYy = sorted(XYI, key=itemgetter(1))
uf = Uf(N)
px = py = -1
for (x, y, i) in XYx:
	if px == x:
		uf.unite(i, pi)
	(px, pi) = (x, i)
for (x, y, i) in XYy:
	if py == y:
		uf.unite(i, pi)
	(py, pi) = (y, i)
ddx = defaultdict(set)
ddy = defaultdict(set)
for (x, y, i) in XYI:
	ddx[uf.root(i)].add(x)
	ddy[uf.root(i)].add(y)
ans = 0
for i in range(N):
	if i == uf.root(i):
		ans += len(ddx[i]) * len(ddy[i])
print(ans - N)


import sys
sys.setrecursionlimit(10 ** 7)
INTMAX = 9223372036854775807
INTMIN = -9223372036854775808
DVSR = 1000000007

def POW(x, y):
	return pow(x, y, DVSR)

def INV(x, d=DVSR):
	return pow(x, d - 2, d)

def DIV(x, y, d=DVSR):
	return x * INV(y, d) % d

def LI():
	return [int(x) for x in input().split()]

def LF():
	return [float(x) for x in input().split()]

def LS():
	return input().split()

def II():
	return int(input())

class UnionFindTree:

	def __init__(self, n):
		self.nodes = list(range(n + 1))

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

	def unite(self, a, b):
		a = self.find(a)
		b = self.find(b)
		if a == b:
			return
		self.nodes[a] = self.nodes[b]

	def is_same(self, a, b):
		return self.find(a) == self.find(b)
UFL = UnionFindTree(100010)
UFR = UnionFindTree(100010)
N = II()
rmap = {}
lmap = {}
for i in range(N):
	(l, r) = LI()
	if not r in rmap:
		rmap[r] = []
	if not l in lmap:
		lmap[l] = []
	rmap[r].append(l)
	lmap[l].append(r)
for (l, lis) in lmap.items():
	for r in lmap[l]:
		for l2 in rmap[r]:
			UFL.unite(l, l2)
for (r, lis) in rmap.items():
	for l in rmap[r]:
		for r2 in lmap[l]:
			UFR.unite(r, r2)
varl = {}
varr = {}
for l in lmap.keys():
	gr = UFL.find(l)
	if gr in varl:
		varl[gr] += 1
	else:
		varl[gr] = 1
for r in rmap.keys():
	gr = UFR.find(r)
	if gr in varr:
		varr[gr] += 1
	else:
		varr[gr] = 1
res = {}
for (l, lis) in lmap.items():
	gl = UFL.find(l)
	gr = UFR.find(lis[0])
	res[gl, gr] = varl[gl] * varr[gr]
ressum = 0
for i in res.values():
	ressum += i
print(ressum - N)


def f_must_be_rectangular(N, Positions):
	import sys
	sys.setrecursionlimit(10 ** 6)
	vertex_maxsize = 10 ** 5 + 5
	target = [[] for _ in range(2 * vertex_maxsize)]
	is_visited = [False] * (2 * vertex_maxsize)
	for (x, y) in Positions:
		y += vertex_maxsize
		target[x].append(y)
		target[y].append(x)

	def dfs(v):
		if is_visited[v]:
			return
		is_visited[v] = True
		count[v // vertex_maxsize] += 1
		for u in target[v]:
			dfs(u)
	ans = 0
	for i in range(2 * vertex_maxsize):
		if is_visited[i]:
			continue
		count = [0] * 2
		dfs(i)
		ans += count[0] * count[1]
	return ans - N
N = int(input())
Positions = [[int(i) for i in input().split()] for j in range(N)]
print(f_must_be_rectangular(N, Positions))


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
		dfs(v)
		ans[0] += cnt[0] * cnt[1]
	print(ans[0] - n)
resolve()


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)


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

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)


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

class Union:

	def __init__(s, n):
		s.N = n
		s.L = [-1] * n

	def root(s, x):
		if s.L[x] < 0:
			return (x, -s.L[x])
		return s.root(s.L[x])

	def merge(s, x, y):
		(a, ar) = s.root(x)
		(b, br) = s.root(y)
		if a == b:
			return True
		if ar < br:
			s.L[b] -= ar
			s.L[a] = b
		else:
			s.L[a] -= br
			s.L[b] = a
		return False

	def getList(s):
		D = []
		DN = [0] * N
		for i in range(N):
			if s.L[i] < 0:
				DN[i] = len(D)
				D.append([])
		for i in range(N):
			D[DN[s.root(i)[0]]].append(i)
		return D
M = 10 ** 5 + 1
xL = [-1] * M
yL = [-1] * M
uni = Union(N)
for i in range(N):
	x = xy[i][0]
	y = xy[i][1]
	if xL[x] != -1:
		uni.merge(xL[x], i)
	if yL[y] != -1:
		uni.merge(yL[y], i)
	xL[x] = i
	yL[y] = i
L = uni.getList()
ans = 0
for t in L:
	xL = [0] * M
	yL = [0] * M
	xN = 0
	yN = 0
	for i in t:
		(x, y) = xy[i]
		if xL[x] == 0:
			xL[x] = 1
			xN += 1
		if yL[y] == 0:
			yL[y] = 1
			yN += 1
	ans += xN * yN - len(t)
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 sys
buff_readline = sys.stdin.readline
readline = sys.stdin.readline
INF = 2 ** 62 - 1

def read_int():
	return int(buff_readline())

def read_int_n():
	return list(map(int, buff_readline().split()))

def read_float():
	return float(buff_readline())

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

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

def read_str_n():
	return readline().strip().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):
	from collections import defaultdict
	g = defaultdict(set)
	yo = 10 ** 6
	V = set()
	for (x, y) in XY:
		y += yo
		g[x].add(y)
		g[y].add(x)
		V.add(x)
		V.add(y)
	rem = set((v for v in V))
	done = set()
	ans = 0
	while rem:
		u = rem.pop()
		s = [u]
		done.add(u)
		xs = set()
		ys = set()
		if u >= yo:
			ys.add(u)
		else:
			xs.add(u)
		while s:
			u = s.pop()
			if u >= yo:
				ys.add(u)
			else:
				xs.add(u)
			for v in g[u]:
				if v in done:
					continue
				s.append(v)
				done.add(v)
		rem -= xs
		rem -= ys
		e = 0
		for u in xs:
			e += len(g[u])
		ans += len(xs) * len(ys) - e
	return ans

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


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

	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
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:
			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
Xi = defaultdict(CountUp())
Yi = defaultdict(CountUp())
N = int(input())
xy = []
for _ in range(N):
	(x, y) = inpl()
	xi = Xi[x]
	yi = Yi[y]
	xy.append((xi, yi))
Nx = len(Xi)
Ny = len(Yi)
Nxy = Nx + Ny
uf = UnionFind(Nx + Ny)
for (xi, yi) in xy:
	uf.merge(xi, yi + Nx)
gi = defaultdict(CountUp())
Ng = uf.groups()
edges = [0] * Ng
nx = [0] * Ng
ny = [0] * Ng
for i in range(Nx):
	nx[gi[uf.root(i)]] += 1
for i in range(Nx, Nxy):
	ny[gi[uf.root(i)]] += 1
for (xi, yi) in xy:
	edges[gi[uf.root(xi)]] += 1
ans = 0
for g in range(Ng):
	ans += nx[g] * ny[g] - edges[g]
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():

	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
		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()
__starting_point()


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)


import sys
reader = (s.rstrip() for s in sys.stdin)
input = reader.__next__
n = int(input())
m = 10 ** 5
G = [[] for i in range(2 * m)]
for i in range(n):
	(x, y) = map(int, input().split())
	(x, y) = (x - 1, y - 1)
	G[x].append(m + y)
	G[m + y].append(x)
from collections import deque
used = [0] * (2 * m)

def dfs(i):
	q = deque([i])
	x = 0
	y = 0
	edge = 0
	while q:
		cur = q.pop()
		if used[cur]:
			continue
		used[cur] = 1
		if cur < m:
			x += 1
		else:
			y += 1
		edge += len(G[cur])
		for to in G[cur]:
			if used[to] == 0:
				q.append(to)
	return x * y - edge // 2
ans = 0
for i in range(2 * m):
	if used[i]:
		continue
	ans += dfs(i)
print(ans)


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)


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())
		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()


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 ** 7)
INF = 10 ** 18
MOD = 10 ** 9 + 7

def YesNo(x):
	return 'Yes' if x else 'No'

def LI():
	return [int(x) for x in sys.stdin.readline().split()]

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 II():
	return int(sys.stdin.readline())

def SI():
	return input()
from collections import defaultdict

def main():
	N = II()
	edges = defaultdict(list)
	for _ in range(N):
		(x, y) = LI()
		edges[x].append(-y)
		edges[-y].append(x)
	visited = defaultdict(int)
	if max((len(li) for li in edges.values())) == 2 and N >= 10 ** 4:
		(m, p) = (0, 0)
		for k in edges.keys():
			if k < 0:
				m += 1
			else:
				p += 1
		return m * p - N

	def solve(v):
		visited[v] = 1
		ret = [v]
		for u in edges[v]:
			if not visited[u]:
				ret += solve(u)
		return ret
	ans = -N
	for node in edges:
		if not visited[node]:
			li = solve(node)
			(m, p) = (0, 0)
			for z in li:
				if z < 0:
					m += 1
				else:
					p += 1
			ans += m * p
	return ans
print(main())


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)


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
sys.setrecursionlimit(10 ** 6)
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)

def DFS(i, XorY=0):
	for next in XY[XorY][i]:
		if visited[1 - XorY][next] == 0:
			visited[1 - XorY][next] = 1
			DFS(next, 1 - XorY)
			count[1 - XorY] += 1
ans = 0
visited = [[0 for _ in range(10 ** 5 + 1)] for __ in range(2)]
for i in range(10 ** 5 + 1):
	if XY[0][i] != [] and visited[0][i] == 0:
		visited[0][i] = 1
		count = [1, 0]
		DFS(i)
		ans += count[0] * count[1]
ans -= N
print(ans)


n = int(input())
xy = {}
for i in range(n):
	(x, y) = map(int, input().split())
	xy[x] = xy.get(x, []) + [y]
d = {}
for x in xy:
	v0 = xy[x][0]
	while d.get(v0, v0) != v0:
		v0 = d[v0]
	for v in xy[x]:
		v1 = d.get(v, v)
		while d.get(v1, v1) != v1:
			v1 = d[v1]
		d[v1] = v0
for y in d:
	yp = d[y]
	y2 = [y]
	while yp != d[yp]:
		y2 += [yp]
		yp = d[yp]
	for y3 in y2:
		d[y3] = yp
e = {}
for y in d:
	e[d[y]] = e.get(d[y], 0) + 1
ans = 0
for x in xy:
	ans += e[d[xy[x][0]]] - len(xy[x])
print(ans)


import sys
from bisect import bisect_left
stdin = sys.stdin
ni = lambda : int(ns())
na = lambda : list(map(int, stdin.readline().split()))
ns = lambda : stdin.readline().rstrip()
n = ni()
upper = [-1] * (2 * n)
nxs = [0] * (2 * n)
for i in range(n):
	nxs[i] += 1

def root(x):
	if upper[x] < 0:
		return x
	else:
		upper[x] = root(upper[x])
		return upper[x]

def equiv(x, y):
	return root(x) == root(y)

def unite(x, y):
	(x, y) = (root(x), root(y))
	if x != y:
		if upper[y] < upper[x]:
			(x, y) = (y, x)
		upper[x] += upper[y]
		nxs[x] += nxs[y]
		upper[y] = x
	return x == y
co = []
for i in range(n):
	co.append(na())
xs = list(set((_[0] for _ in co)))
xs.sort()
ys = list(set((_[1] for _ in co)))
ys.sort()
for c in co:
	unite(bisect_left(xs, c[0]), bisect_left(ys, c[1]) + n)
ans = 0
for i in range(2 * n):
	if upper[i] < 0:
		ans += nxs[i] * (-upper[i] - nxs[i])
ans -= n
print(ans)


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]) = 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
input = sys.stdin.readline

class UnionFind:

	def __init__(self, n):
		self.n = n
		self.root = [-1] * (n + 1)
		self.rnk = [0] * (n + 1)

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

	def unite(self, x, y):
		x = self.find_root(x)
		y = self.find_root(y)
		if x == y:
			return
		elif self.rnk[x] > self.rnk[y]:
			self.root[x] += self.root[y]
			self.root[y] = x
		else:
			self.root[y] += self.root[x]
			self.root[x] = y
			if self.rnk[x] == self.rnk[y]:
				self.rnk[y] += 1

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

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

def main():
	N = int(input())
	xy_list = []
	x_list = []
	y_list = []
	for i in range(N):
		(x, y) = map(int, input().split())
		xy_list.append((x, y))
		x_list.append(x)
		y_list.append(y)
	x_list = list(set(x_list))
	y_list = list(set(y_list))
	x_list.sort()
	y_list.sort()
	dictX = {}
	dictY = {}
	for (i, x) in enumerate(x_list):
		dictX[x] = i
	for (i, y) in enumerate(y_list):
		dictY[y] = i
	xy_c = [[] for _ in range(len(x_list))]
	for (x, y) in xy_list:
		(xc, yc) = (dictX[x], dictY[y])
		xy_c[xc].append(yc)
	UF = UnionFind(len(y_list) + 1)
	for i in range(len(x_list)):
		j_list = xy_c[i]
		if len(j_list) > 1:
			j0 = j_list[0]
			for j in j_list[1:]:
				UF.unite(j0, j)
	ans = 0
	for i in range(len(x_list)):
		j0 = xy_c[i][0]
		ans += UF.size(j0) - len(xy_c[i])
	print(ans)
main()


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) = 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 R.values():
	s += r[0] * r[1]
print(s - N)


class UnionFind:

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

	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
from collections import defaultdict
N = int(input())
u = UnionFind(N)
xy = [list(map(int, input().split())) for _ in range(N)]
x_d = defaultdict(lambda : [])
y_d = defaultdict(lambda : [])
for i in range(N):
	(x, y) = xy[i]
	x_d[x].append(i)
	y_d[y].append(i)
for x in x_d:
	j = x_d[x][0]
	for i in x_d[x]:
		u.union(i, j)
for y in y_d:
	j = y_d[y][0]
	for i in y_d[y]:
		u.union(i, j)
d = defaultdict(lambda : [])
for i in range(N):
	d[u.find(i)].append(i)
ans = 0
for f in d:
	x_l = []
	y_l = []
	for i in d[f]:
		(x, y) = xy[i]
		x_l.append(x)
		y_l.append(y)
	x_s = set(x_l)
	y_s = set(y_l)
	ans += len(x_s) * len(y_s) - len(d[f])
print(ans)


class UnionFind:

	def __init__(self, n, xy):
		self.parent = list(range(n))
		self.size = [1] * n
		self.xval = [{x} for (x, y) in xy]
		self.yval = [{y} for (x, y) in xy]

	def root(self, x):
		while self.parent[x] != x:
			(x, self.parent[x]) = (self.parent[x], self.parent[self.parent[x]])
		return x

	def merge(self, x, y):
		(x, y) = (self.root(x), self.root(y))
		if x == y:
			return False
		if self.size[x] < self.size[y]:
			self.size[y] += self.size[x]
			self.parent[x] = y
			self.xval[y].update(self.xval[x])
			self.yval[y].update(self.yval[x])
		else:
			self.size[x] += self.size[y]
			self.parent[y] = x
			self.xval[x].update(self.xval[y])
			self.yval[x].update(self.yval[y])
		return True

	def issame(self, x, y):
		return self.root(x) == self.root(y)

	def size(self, x):
		return self.size[self.root(x)]
n = int(input())
xy = [[int(i) for i in input().split()] for j in range(n)]
UF = UnionFind(n, xy)
xval = dict()
yval = dict()
for (i, (x, y)) in enumerate(xy):
	if y in yval:
		UF.merge(i, yval[y])
	else:
		yval[y] = i
	if x in xval:
		UF.merge(i, xval[x])
	else:
		xval[x] = i
ans = -n
for (i, pi) in enumerate(UF.parent):
	if i == pi:
		ans += len(UF.xval[pi]) * len(UF.yval[pi])
print(ans)


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())
		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()


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)


from collections import defaultdict

class UnionFind:

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

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

	def unite(self, x, y):
		x = self.find(x)
		y = self.find(y)
		self.par[x] = y

	def same(self, x, y):
		return self.find(x) == self.find(y)
n = int(input())
uf = UnionFind(n)
xs = []
ys = []
x_dict = {}
y_dict = {}
for i in range(n):
	(x, y) = map(int, input().split())
	xs.append(x)
	ys.append(y)
	if x in x_dict:
		uf.unite(i, x_dict[x])
	if y in y_dict:
		uf.unite(i, y_dict[y])
	x_dict[x] = uf.find(i)
	y_dict[y] = uf.find(i)
xi = defaultdict(list)
yi = defaultdict(list)
for i in range(n):
	xi[uf.find(x_dict[xs[i]])].append(xs[i])
	yi[uf.find(y_dict[ys[i]])].append(ys[i])
ans = 0
for k in xi.keys():
	ans += len(set(xi[k])) * len(set(yi[k])) - len(xi[k])
print(ans)


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

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):
		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) = map(int, read().split())
	P = [(x, y) for (x, y) in zip(*[iter(XY)] * 2)]
	uf = UnionFind(N)
	Q = [(x, y, i) for (i, (x, y)) in enumerate(P)]
	Q.sort()
	for j in range(N - 1):
		if Q[j][0] == Q[j + 1][0]:
			uf.union(Q[j][2], Q[j + 1][2])
	Q.sort(key=itemgetter(1))
	for j in range(N - 1):
		if Q[j][1] == Q[j + 1][1]:
			uf.union(Q[j][2], Q[j + 1][2])
	ans = 0
	for vec in uf.all_group_members().values():
		x_points = len(set((P[i][0] for i in vec)))
		y_points = len(set((P[i][1] for i in vec)))
		ans += x_points * y_points - len(vec)
	print(ans)
	return
main()


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
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()


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)
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)


N = int(input())
D = 100000
d = {i: [] for i in range(1, 2 * D + 1)}
cd = set()
used = [0] * (2 * D + 1)
ans = 0
for _ in range(N):
	(x, y) = map(int, input().split())
	y += D
	cd.add(x)
	d[x].append(y)
	d[y].append(x)
for x in cd:
	if used[x]:
		continue
	q = [x]
	used[x] = 1
	(cx, sx) = (0, 0)
	while q:
		a = q.pop()
		if a <= D:
			cx += 1
		sx += 1
		for b in d[a]:
			if used[b]:
				continue
			used[b] = 1
			q.append(b)
	ans += cx * (sx - cx)
print(ans - N)


import sys
sys.setrecursionlimit(10 ** 6)

def input():
	return sys.stdin.readline().strip()

def main():
	N = int(input())
	V = 10 ** 5 + 1
	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]

	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()


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) = 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
ans = 0
for i in range(2 * MAX):
	ans += num_x[i] * num_y[i]
print(ans - N)


class UnionFindTree:

	def __init__(self, n):
		self.par = list(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 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) = 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 x_dict.keys():
	ans += x_dict.get(r, 0) * y_dict.get(r, 0)
print(ans)


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

class UnionFind:

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

	def find(self, x):
		if self.parent[x] != x:
			self.parent[x] = self.find(self.parent[x])
		return self.parent[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.parent[x] = y
			self.size[y] += self.size[x]
		else:
			self.parent[y] = x
			self.size[x] += self.size[y]
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

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

	def get_size(self, x):
		return self.size[self.find(x)]
N = int(input())
a = [-1] * (10 ** 5 + 1)
uf = UnionFind(10 ** 5 + 1)
xs = [set() for _ in range(10 ** 5 + 1)]
for _ in range(N):
	(x, y) = map(int, input().split())
	if a[x] == -1:
		a[x] = y
	else:
		uf.unite(a[x], y)
	xs[y].add(x)
root = defaultdict(set)
for y in range(10 ** 5 + 1):
	root[uf.find(y)] |= xs[y]
ans = 0
for y in range(10 ** 5 + 1):
	ans += len(root[uf.find(y)]) - len(xs[y])
print(ans)


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) = 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)


from bisect import bisect_left
N = int(input())
e5 = 10 ** 5
v = [[] for _ in range(2 * e5)]
visited = [True] * (2 * e5)
for _ in range(N):
	(x, y) = map(int, input().split())
	(x, y) = (x - 1, y - 1)
	v[x].append(y + e5)
	v[y + e5].append(x)
	visited[x] = visited[y + e5] = False
ret = 0
for i in range(e5):
	if not visited[i]:
		queue = [i]
		visited[i] = True
		(nx, ny) = (1, 0)
		while queue:
			q = queue.pop()
			for j in v[q]:
				if not visited[j]:
					visited[j] = True
					queue.append(j)
					if j < e5:
						nx += 1
					else:
						ny += 1
		ret += nx * ny
print(ret - N)


import sys
sys.setrecursionlimit(100000 + 10)

def add_edge(u, v):
	G[u].append(v)
	G[v].append(u)

def dfs(vertex, x_set, y_set):
	global visited
	if visited[vertex]:
		return 0
	visited[vertex] = True
	x_set.add(coords[vertex][1])
	y_set.add(coords[vertex][2])
	for v in G[vertex]:
		if visited[v]:
			pass
		else:
			dfs(v, x_set, y_set)
	return len(x_set) * len(y_set)
n = int(input())
G = [[] for _ in range(n)]
visited = [False] * n
now_index = n - 1
coords = [[i] + list(map(int, input().split())) for i in range(n)]
from operator import itemgetter
coords_xs = sorted(coords, key=itemgetter(1))
for i in range(n - 1):
	if coords_xs[i][1] == coords_xs[i + 1][1]:
		add_edge(coords_xs[i][0], coords_xs[i + 1][0])
coords_ys = sorted(coords, key=itemgetter(2))
for i in range(n - 1):
	if coords_ys[i][2] == coords_ys[i + 1][2]:
		add_edge(coords_ys[i][0], coords_ys[i + 1][0])
final = 0
for i in range(n):
	x_set = {coords[i][1]}
	y_set = {coords[i][2]}
	final += dfs(i, x_set, y_set)
print(final - n)


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:
			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)


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()


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:
			y_res += 1
		else:
			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)


n = int(input())
xy = [list(map(int, input().split())) for i in range(n)]
from collections import defaultdict
x_d = defaultdict(list)
y_d = defaultdict(list)
for i in range(n):
	(x, y) = xy[i]
	x_d[x].append(i)
	y_d[y].append(i)

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):
		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) 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())
uf = UnionFind(n)
for k in x_d.keys():
	if len(x_d[k]) == 1:
		continue
	last = x_d[k][0]
	for itm in x_d[k]:
		uf.union(last, itm)
		last = itm
for k in y_d.keys():
	if len(y_d[k]) == 1:
		continue
	last = y_d[k][0]
	for itm in y_d[k]:
		uf.union(last, itm)
		last = itm
ans = 0
for root in uf.roots():
	mems = uf.members(root)
	xset = set()
	yset = set()
	for m in mems:
		(x, y) = xy[m]
		xset.add(x)
		yset.add(y)
	points = len(xset) * len(yset)
	points -= len(mems)
	ans += points
print(ans)


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)


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)]
		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)
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)


import sys
sys.setrecursionlimit(10 ** 6)

def main():
	input = sys.stdin.readline
	V = 10 ** 5
	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
input = sys.stdin.readline
from collections import defaultdict

class UnionFind:

	def __init__(self, n):
		self.par = [i for i 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 union(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
		else:
			self.par[y] = x
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

	def same_check(self, x, y):
		return self.find(x) == self.find(y)
n = int(input())
uf = UnionFind(n)
XY = [tuple(map(int, input().split())) for _ in range(n)]
Xd = defaultdict(list)
Yd = defaultdict(list)
for (i, (x, y)) in enumerate(XY):
	Xd[x].append(i)
	Yd[y].append(i)
for x in Xd:
	i = Xd[x][0]
	for j in Xd[x]:
		uf.union(i, j)
for y in Yd:
	i = Yd[y][0]
	for j in Yd[y]:
		uf.union(i, j)
d = defaultdict(list)
for i in range(n):
	d[uf.find(i)].append(i)
ans = 0
for k in d:
	(Xs, Ys) = (set(), set())
	for i in d[k]:
		(x, y) = XY[i]
		Xs.add(x)
		Ys.add(y)
	ans += len(Xs) * len(Ys)
ans -= n
print(ans)


from collections import defaultdict
import sys
input = sys.stdin.readline
n = int(input())
xy = [list(map(int, input().split())) for i in range(n)]
graph = [[] for i in range(2 * 10 ** 5 + 1)]
ans = 0
for (x, y) in xy:
	y += 10 ** 5
	graph[x].append(y)
	graph[y].append(x)
	ans -= 1
vis = [0 for i in range(2 * 10 ** 5 + 1)]
for i in range(1, 2 * 10 ** 5 + 1):
	if vis[i]:
		continue
	stack = [i]
	nx = 0
	ny = 0
	while stack:
		x = stack.pop()
		if x > 10 ** 5:
			ny += 1
		else:
			nx += 1
		vis[x] = 1
		for y in graph[x]:
			if not vis[y]:
				stack.append(y)
				vis[y] = 1
	ans += nx * ny
print(ans)


import sys
sys.setrecursionlimit(10 ** 6)
N = int(input())
nmax = 100001
xys = [[[] for i in range(nmax)] for j in range(2)]
used = [[0 for i in range(nmax)] for j in range(2)]
for _ in range(N):
	(x, y) = map(int, input().split())
	xys[0][x].append(y)
	xys[1][y].append(x)

def dfs(i, el=0):
	for i in xys[el][i]:
		if not used[1 - el][i]:
			used[1 - el][i] = True
			dfs(i, 1 - el)
			cnt[1 - el] += 1
ans = 0
for i in range(nmax):
	if xys[0][i] != [] and used[0][i] == 0:
		used[0][i] = 1
		cnt = [1, 0]
		dfs(i, 0)
		ans += cnt[0] * cnt[1]
print(ans - N)


import sys
input = sys.stdin.readline

def main():
	N = int(input())
	XY = [list(map(int, input().split())) for _ in range(N)]
	MAX = 10 ** 5
	Xs = [[] for _ in range(MAX + 1)]
	Ys = [[] for _ in range(MAX + 1)]
	for (i, (x, y)) in enumerate(XY):
		Xs[x].append(i)
		Ys[y].append(i)
	ans = 0
	checked = [False] * N
	for i in range(N):
		if checked[i]:
			continue
		q = [i]
		checked[i] = True
		X = set()
		Y = set()
		c = 0
		while q:
			qq = []
			for p in q:
				(x, y) = XY[p]
				X.add(x)
				Y.add(y)
				c += 1
				for ni in Xs[x]:
					if not checked[ni]:
						checked[ni] = True
						qq.append(ni)
				for ni in Ys[y]:
					if not checked[ni]:
						checked[ni] = True
						qq.append(ni)
			q = qq
		ans += len(X) * len(Y) - c
	print(ans)
main()


from collections import defaultdict
import sys
sys.setrecursionlimit(10 ** 9)
n = int(input())
data = []
for i in range(n):
	data.append(list(map(int, input().split())))

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):
		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) 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())
unionfind = UnionFind(n)
x_d = defaultdict(set)
y_d = defaultdict(set)
for i in range(n):
	x_d[data[i][0]].add(i)
	y_d[data[i][1]].add(i)
for i in range(n):
	(x, y) = data[i]
	if len(x_d[x]) > 1 and len(y_d[y]) > 1:
		for j in x_d[x]:
			unionfind.union(i, j)
		for j in y_d[y]:
			unionfind.union(i, j)
point = [[] for i in range(n)]
for i in range(n):
	if unionfind.size(i) <= 2:
		continue
	else:
		point[unionfind.find(i)].append(i)
ans = 0
for i in range(n):
	if len(point[i]) > 2:
		x_s = set()
		y_s = set()
		for j in point[i]:
			(x, y) = data[j]
			x_s.add(x)
			y_s.add(y)
		ans += len(x_s) * len(y_s) - len(point[i])
print(ans)


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
	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:
			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()


class UnionFind:

	def __init__(self, n):
		self.parent = list(range(n))
		self.size = [1] * n

	def root(self, x):
		while self.parent[x] != x:
			(x, self.parent[x]) = (self.parent[x], self.parent[self.parent[x]])
		return x

	def merge(self, x, y):
		(x, y) = (self.root(x), self.root(y))
		if x == y:
			return False
		if self.size[x] < self.size[y]:
			self.size[y] += self.size[x]
			self.parent[x] = y
		else:
			self.size[x] += self.size[y]
			self.parent[y] = x
		return True

	def issame(self, x, y):
		return self.root(x) == self.root(y)

	def size(self, x):
		return self.size[self.root(x)]
n = int(input())
xy = [[int(i) for i in input().split()] for j in range(n)]
UF = UnionFind(n)
xval = dict()
yval = dict()
for (i, (x, y)) in enumerate(xy):
	if y in yval:
		UF.merge(i, yval[y])
	else:
		yval[y] = i
	if x in xval:
		UF.merge(i, xval[x])
	else:
		xval[x] = i
xcon = dict()
ycon = dict()
for i in range(n):
	pi = UF.root(i)
	(x, y) = xy[i]
	if pi in xcon:
		xcon[pi].add(x)
	else:
		xcon[pi] = {x}
	if pi in ycon:
		ycon[pi].add(y)
	else:
		ycon[pi] = {y}
ans = -n
for (key, val) in xcon.items():
	ans += len(val) * len(ycon[key])
print(ans)


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))

def find(A, x):
	p = A[x]
	if p == x:
		return x
	a = find(A, p)
	A[x] = a
	return a

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
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)]
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)


from itertools import groupby
from operator import itemgetter
N = int(input())
(x, y) = zip(*(map(int, input().split()) for _ in range(N))) if N else ((), ())

class UnionFindTree:

	def __init__(self, n):
		self.p = [i for i in range(n + 1)]
		self.r = [0 for _ in range(n + 1)]

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

	def union(self, x, y):
		px = self.find(x)
		py = self.find(y)
		if px != py:
			if self.r[px] < self.r[py]:
				self.p[px] = py
			else:
				self.p[py] = px
				if self.r[px] == self.r[py]:
					self.r[px] += 1

	def same(self, x, y):
		return self.find(x) == self.find(y)
uft = UnionFindTree(N)
for d in range(2):
	for (_, g) in groupby(sorted(zip(x, y, range(1, N + 1)), key=itemgetter(d)), key=itemgetter(d)):
		p = list(g)
		for ((_, _, i), (_, _, j)) in zip(p[1:], p):
			uft.union(i, j)
ans = sum((len({x for (x, _, _) in C}) * len({y for (_, y, _) in C}) - len(list(C)) for C in (list(C) for (_, C) in groupby(sorted(((x, y, uft.find(i)) for (x, y, i) in zip(x, y, range(1, N + 1))), key=itemgetter(2)), key=itemgetter(2)))))
print(ans)


def main():
	import sys
	input = sys.stdin.readline
	sys.setrecursionlimit(10 ** 7)
	from collections import Counter, deque
	from itertools import combinations, permutations, accumulate, groupby, product
	from bisect import bisect_left, bisect_right
	from heapq import heapify, heappop, heappush
	import math
	n = int(input())
	visited_x = [0] * (10 ** 5 + 1)
	visited_y = [0] * (10 ** 5 + 1)
	adj_x = [[] for _ in range(10 ** 5 + 1)]
	adj_y = [[] for _ in range(10 ** 5 + 1)]
	for _ in range(n):
		(x, y) = map(int, input().split())
		adj_x[x].append(y)
		adj_y[y].append(x)
	res = 0
	for x in range(1, 10 ** 5 + 1):
		if visited_x[x] or not adj_x[x]:
			continue
		visited_x[x] = 1
		vx = 1
		vy = 0
		d = deque([[x, 0]])
		while d:
			(v, axis) = d.popleft()
			if axis == 0:
				for nv in adj_x[v]:
					if visited_y[nv]:
						continue
					visited_y[nv] = 1
					vy += 1
					d.append([nv, 1])
			else:
				for nv in adj_y[v]:
					if visited_x[nv]:
						continue
					visited_x[nv] = 1
					vx += 1
					d.append([nv, 0])
		res += vx * vy
	print(res - n)
main()


from bisect import bisect_left
from collections import defaultdict
N = int(input())
points = list((tuple(map(int, input().split())) for _ in range(N)))
xty = defaultdict(list)
ytx = defaultdict(list)
for (x, y) in points:
	xty[x].append(y)
	ytx[y].append(x)
area = 0
X = set()
Y = set()
for (x, y) in points:
	if x in X or y in Y:
		continue
	stack_x = [x]
	stack_y = [y]
	dx = set()
	dy = set()
	while stack_x or stack_y:
		while stack_x:
			x = stack_x.pop()
			if x not in dx:
				dx.add(x)
				for y in xty[x]:
					if y not in dy:
						stack_y.append(y)
		while stack_y:
			y = stack_y.pop()
			if y not in dy:
				dy.add(y)
				for x in ytx[y]:
					if x not in dx:
						stack_x.append(x)
	X |= dx
	Y |= dy
	area += len(dx) * len(dy)
print(area - len(points))


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
input = sys.stdin.readline
sys.setrecursionlimit(200010)
n = int(input())
info = [list(map(int, input().split())) for i in range(n)]
v = 100000
graph = [[] for i in range(v * 2)]
for i in range(n):
	x = info[i][0] - 1
	y = info[i][1] - 1 + v
	graph[x].append(y)
	graph[y].append(x)

def dfs(pos):
	if visited[pos]:
		return
	visited[pos] = True
	cnt[pos // v] += 1
	for i in graph[pos]:
		dfs(i)
	return
visited = [False] * (v * 2)
cnt = [0] * 2
ans = 0
for i in range(v * 2):
	cnt[0] = 0
	cnt[1] = 0
	dfs(i)
	ans += cnt[0] * cnt[1]
print(ans - n)


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]
	count += len(set(point_dict['x'])) * len(set(point_dict['y']))
print(count - len(points))


N = int(input())
from collections import defaultdict
X = dict()
Y = dict()
P = []
root = [-1] * N

def find(i):
	global root
	if root[i] >= 0:
		root[i] = find(root[i])
		return root[i]
	return i
for i in range(N):
	(x, y) = map(int, input().split())
	P += [(x, y)]
	if x not in X.keys():
		rx = i
		X[x] = i
	else:
		rx = find(X[x])
		X[x] = rx
	if y not in Y.keys():
		ry = i
		Y[y] = i
	else:
		ry = find(Y[y])
		Y[y] = ry
	if rx == ry:
		if i != rx:
			root[i] = rx
		continue
	elif root[rx] <= root[ry]:
		root[rx] += root[ry]
		root[ry] = rx
		if i != rx:
			root[i] = rx
	else:
		root[ry] += root[rx]
		root[rx] = ry
		if i != ry:
			root[i] = ry
cntx = defaultdict(set)
cnty = defaultdict(set)
RP = set()
ans = 0
for i in range(N):
	(x, y) = P[i]
	r = find(i)
	cntx[r] |= {x}
	cnty[r] |= {y}
	RP |= {r}
for k in RP:
	ans += len(cntx[k]) * len(cnty[k])
print(ans - N)


class UnionFind(object):

	def __init__(self, size):
		self.parent = [i for i in range(size)]
		self.rank = [0 for _ in range(size)]
		self.size = [1 for _ in range(size)]

	def find(self, x):
		if self.parent[x] == x:
			return x
		else:
			return self.find(self.parent[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.parent[x] = y
			self.size[y] += self.size[x]
		else:
			self.parent[y] = x
			self.size[x] += self.size[y]
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

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

	def get_size(self, x):
		x = self.find(x)
		return self.size[x]
N = int(input())
U = UnionFind(N)
xyi = []
xy = []
for i in range(N):
	(x, y) = map(int, input().split())
	xyi.append((x, y, i))
	xy.append((x, y))
xyi.sort()
for j in range(N - 1):
	(x0, y0, i0) = xyi[j]
	(x1, y1, i1) = xyi[j + 1]
	if x0 == x1:
		U.unite(i0, i1)
xyi.sort(key=lambda x: x[1])
for j in range(N - 1):
	(x0, y0, i0) = xyi[j]
	(x1, y1, i1) = xyi[j + 1]
	if y0 == y1:
		U.unite(i0, i1)
equiv = [[set(), set()] for _ in range(N)]
for i in range(N):
	p = U.find(i)
	equiv[p][0].add(xy[i][0])
	equiv[p][1].add(xy[i][1])
ans = 0
for i in range(N):
	ans += len(equiv[i][0]) * len(equiv[i][1])
ans -= N
print(ans)


import sys
from collections import deque
from collections import defaultdict
from heapq import heapify, heappush, heappop, heappushpop
from bisect import bisect_left, bisect_right
sys.setrecursionlimit(int(10000000.0))
s2nn = lambda s: [int(c) for c in s.split(' ')]
ss2nn = lambda ss: [int(s) for s in ss]
ss2nnn = lambda ss: [s2nn(s) for s in ss]
i2s = lambda : sys.stdin.readline().rstrip()
i2n = lambda : int(i2s())
i2nn = lambda : s2nn(i2s())
ii2ss = lambda n: [sys.stdin.readline().rstrip() for _ in range(n)]
ii2nn = lambda n: ss2nn(ii2ss(n))
ii2nnn = lambda n: ss2nnn(ii2ss(n))

def main():
	N = i2n()
	XY = ii2nnn(N)
	M = int(100000.0) + 5
	to = [[] for _ in range(M * 2)]
	cnt = [0] * (M * 2)
	visited = [False] * (M * 2)
	for (x, y) in XY:
		y += M
		to[x].append(y)
		to[y].append(x)
	cnt = [0, 0]

	def bfs(n):
		q = deque()
		q.append(n)
		while q:
			i = q.popleft()
			if visited[i]:
				continue
			visited[i] = True
			cnt[i // M] += 1
			for j in to[i]:
				q.append(j)
	ans = 0
	for i in range(M * 2):
		if visited[i]:
			continue
		cnt[0] = 0
		cnt[1] = 0
		bfs(i)
		ans += cnt[0] * cnt[1]
	ans -= N
	print(ans)
main()


from collections import defaultdict

class UnionFind:

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

	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
N = int(input())
dx = defaultdict(list)
dy = defaultdict(list)
xy = []
for i in range(N):
	(x, y) = map(int, input().split())
	dx[x].append(i)
	dy[y].append(i)
	xy.append([x, y])
u = UnionFind(N)
for x in dx:
	i = dx[x][0]
	for j in dx[x]:
		u.union(i, j)
for y in dy:
	i = dy[y][0]
	for j in dy[y]:
		u.union(i, j)
dx = defaultdict(list)
dy = defaultdict(list)
for i in range(N):
	dx[u.find(i)].append(xy[i][0])
	dy[u.find(i)].append(xy[i][1])
ans = -N
for i in dx:
	ans += len(set(dx[i])) * len(set(dy[i]))
print(ans)


import sys
from collections import defaultdict
sys.setrecursionlimit(2 * 10 ** 6)

class UF:

	def __init__(self, num):
		self.par = [-1] * num
		self.size = [1] * num

	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.size[rx] += self.size[ry]
			elif self.par[rx] > self.par[ry]:
				self.par[rx] = ry
				self.size[ry] += self.size[rx]
			else:
				self.par[rx] -= 1
				self.par[ry] = rx
				self.size[rx] += self.size[ry]
		return
N = int(input())
XY = [tuple(map(int, sys.stdin.readline().split())) for _ in range(N)]
(X, Y) = list(map(list, zip(*XY)))
G = UF(N)
checked = [False] * N
Xc = defaultdict(list)
Yc = defaultdict(list)
for i in range(N):
	Xc[X[i]].append(i)
	Yc[Y[i]].append(i)
for v in Xc.values():
	if len(v) == 1:
		continue
	vx = v[0]
	for x in v[1:]:
		G.union(vx, x)
for v in Yc.values():
	if len(v) == 1:
		continue
	vy = v[0]
	for y in v[1:]:
		G.union(vy, y)
ans = -N
Zx = defaultdict(set)
Zy = defaultdict(set)
for i in range(N):
	ri = G.find(i)
	Zx[ri].add(X[i])
	Zy[ri].add(Y[i])
for i in range(N):
	ri = G.find(i)
	if checked[ri] == True:
		continue
	checked[ri] = True
	ans += len(Zx[ri]) * len(Zy[ri])
print(ans)


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)


class UnionFind:

	def __init__(self, n):
		self.par = [-1] * n
		self.rank = [0] * n

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

	def unite(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[x] += self.par[y]
			self.par[y] = x

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

	def size(self, x):
		return -self.par[self.find(x)]
import operator
n = int(input())
z = [list(map(int, input().split())) for i in range(n)]
for i in range(n):
	z[i].append(i)
u = UnionFind(n)
z.sort()
for i in range(1, n):
	if z[i][0] == z[i - 1][0]:
		u.unite(z[i][2], z[i - 1][2])
z.sort(key=operator.itemgetter(1))
for i in range(1, n):
	if z[i][1] == z[i - 1][1]:
		u.unite(z[i][2], z[i - 1][2])
l = [[] for i in range(n)]
z.sort(key=operator.itemgetter(2))
for i in range(n):
	l[u.find(i)].append(z[i])
ans = 0
for i in range(n):
	if l[i] != []:
		x = set()
		y = set()
		for a in l[i]:
			if a[0] not in x:
				x.add(a[0])
			if a[1] not in y:
				y.add(a[1])
		ans += len(x) * len(y) - len(l[i])
print(ans)


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):
				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))
	x = [int()] * N
	y = [int()] * N
	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())
U = 10 ** 5
root_x = [None] * (U + 1)
root_y = [None] * (U + 1)
size_x = [0] * (U + 1)
size_y = [0] * (U + 1)
cnt_x = [0] * (U + 1)
cnt_y = [0] * (U + 1)
x_to_y = [None] * (U + 1)
y_to_x = [None] * (U + 1)

def find_root(root, size, x):
	y = root[x]
	if y is None:
		root[x] = x
		size[x] = 1
		return x
	if x == y:
		return y
	z = find_root(root, size, y)
	root[x] = z
	return z

def merge(root, size, cnt, x, y):
	rx = find_root(root, size, x)
	ry = find_root(root, size, y)
	if rx == ry:
		return
	sx = size[rx]
	sy = size[ry]
	if sx > sy:
		root[ry] = rx
		size[rx] += sy
		cnt[rx] += cnt[ry]
	else:
		root[rx] = ry
		size[ry] += sx
		cnt[ry] += cnt[rx]
	return
for _ in range(N):
	(x, y) = map(int, input().split())
	rx = find_root(root_x, size_x, x)
	ry = find_root(root_y, size_y, y)
	cnt_x[rx] += 1
	cnt_y[ry] += 1
	if x_to_y[rx] is None:
		x_to_y[rx] = ry
	if y_to_x[ry] is None:
		y_to_x[ry] = rx
	merge(root_y, size_y, cnt_y, x_to_y[rx], ry)
	merge(root_x, size_x, cnt_x, y_to_x[ry], rx)
	x_to_y[root_x[rx]] = root_y[ry]
	y_to_x[root_y[ry]] = root_x[rx]
answer = 0
for x in range(1, U + 1):
	if root_x[x] is None:
		continue
	rx = find_root(root_x, size_x, x)
	if x != rx:
		continue
	y = x_to_y[x]
	ry = find_root(root_y, size_y, y)
	sx = size_x[rx]
	sy = size_y[ry]
	cnt = cnt_x[rx]
	answer += sx * sy - cnt
print(answer)


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):
		self.table = [i for i in range(N)]
		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):
		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):
		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)


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)


n = int(input())
pos_max = 100000
dic = dict()
for _ in range(n):
	(x, y) = map(int, input().split())
	y += pos_max
	if not x in dic:
		dic[x] = set()
	if not y in dic:
		dic[y] = set()
	dic[x].add(y)
	dic[y].add(x)
checked = set()
count = 0
for x in dic:
	if x in checked:
		continue
	checked.add(x)
	member = {x}
	next_items = {x}
	while next_items:
		v = next_items.pop()
		member |= dic[v]
		next_items |= dic[v] - checked
		checked |= dic[v]
	x_n = sum((w <= pos_max for w in member))
	y_n = sum((w > pos_max for w in member))
	count += x_n * y_n
print(count - n)


class UF:

	def __init__(self, N):
		self._parent = [n for n in range(0, N)]
		self._size = [1] * N

	def find_root(self, x):
		if self._parent[x] == x:
			return x
		self._parent[x] = self.find_root(self._parent[x])
		return self._parent[x]

	def unite(self, x, y):
		gx = self.find_root(x)
		gy = self.find_root(y)
		if gx == gy:
			return
		if self._size[gx] < self._size[gy]:
			self._parent[gx] = gy
			self._size[gy] += self._size[gx]
		else:
			self._parent[gy] = gx
			self._size[gx] += self._size[gy]

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

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

	def groupnum(self):
		N = len(self._parent)
		ans = 0
		for i in range(N):
			if self.find_root(i) == i:
				ans += 1
		return ans
import sys
input = sys.stdin.readline
n = int(input())
UF = UF(2 * 10 ** 5)
for i in range(n):
	(x, y) = map(int, input().split())
	UF.unite(x - 1 + 10 ** 5, y - 1)
UF.groupnum()
ct = [[0, 0] for i in range(2 * 10 ** 5)]
for i in range(2 * 10 ** 5):
	if i < 10 ** 5:
		ct[UF._parent[i]][0] += 1
	else:
		ct[UF._parent[i]][1] += 1
ans = 0
for i in range(2 * 10 ** 5):
	ans += ct[i][0] * ct[i][1]
print(ans - n)


import sys
sys.setrecursionlimit(10 ** 7)
from collections import defaultdict
import math
mod = 10 ** 9 + 7

def I():
	return int(input())

def II():
	return map(int, input().split())

def L():
	return list(map(int, input().split()))

def TWO(M):
	L = [0] * M
	R = [0] * M
	for i in range(M):
		(L[i], R[i]) = II()
	return (L, R)

class UF(object):

	def __init__(self, n=1):
		self.par = [i for i in range(n)]
		self.rank = [0 for _ in range(n)]
		self.size = [1 for _ in range(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 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.size[x] += self.size[y]

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

	def get_size(self, x):
		x = self.find(x)
		return self.size[x]
N = I()
(x, y) = TWO(N)
G = UF(N)
X = defaultdict(list)
Y = defaultdict(list)
for i in range(N):
	X[x[i]].append(i)
	Y[y[i]].append(i)
for v in X.values():
	if len(v) == 1:
		continue
	for i in v[1:]:
		G.union(v[0], i)
for v in Y.values():
	if len(v) == 1:
		continue
	for i in v[1:]:
		G.union(v[0], i)
X2 = defaultdict(set)
Y2 = defaultdict(set)
for i in range(N):
	root = G.find(i)
	X2[root].add(x[i])
	Y2[root].add(y[i])
checked = [False] * N
ans = -N
for i in range(N):
	root = G.find(i)
	if checked[root] == True:
		continue
	checked[root] = True
	ans += len(X2[root]) * len(Y2[root])
print(ans)


from collections import defaultdict
U = 10 ** 5
root = [None] * (2 * U + 1)
N = int(input())
XY = [[int(x) for x in input().split()] for _ in range(N)]

def find_root(x):
	y = root[x]
	if y is None:
		root[x] = x
		return x
	if x == y:
		return y
	z = find_root(y)
	root[x] = z
	return z

def merge(x, y):
	rx = find_root(x)
	ry = find_root(y)
	if rx == ry:
		return
	root[ry] = rx
	return
for (x, y) in XY:
	merge(x, U + y)
pts_cnt = defaultdict(int)
x_cnt = defaultdict(int)
y_cnt = defaultdict(int)
for x in range(U + 1):
	if root[x] is None:
		continue
	x_cnt[find_root(x)] += 1
for y in range(U + 1, U + U + 1):
	if root[y] is None:
		continue
	y_cnt[find_root(y)] += 1
for (x, y) in XY:
	pts_cnt[find_root(x)] += 1
answer = 0
for (k, v) in pts_cnt.items():
	answer += x_cnt[k] * y_cnt[k] - v
print(answer)


class UnionFind:

	def __init__(self, n):
		self.n = n
		self.root = [-1] * (n + 1)
		self.rnk = [0] * (n + 1)

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

	def Unite(self, x, y):
		x = self.Find_Root(x)
		y = self.Find_Root(y)
		if x == y:
			return
		elif self.rnk[x] > self.rnk[y]:
			self.root[x] += self.root[y]
			self.root[y] = x
		else:
			self.root[y] += self.root[x]
			self.root[x] = y
			if self.rnk[x] == self.rnk[y]:
				self.rnk[y] += 1

	def isSameGroup(self, x, y):
		return self.Find_Root(x) == self.Find_Root(y)

	def Count(self, x):
		return -self.root[self.Find_Root(x)]
m = 100010
uf = UnionFind(m * 2)
n = int(input())
for i in range(n):
	(x, y) = map(int, input().split())
	y = y + m
	uf.Unite(x, y)
lx = {}
ly = {}
ans = 0
for i in range(m):
	rx = uf.Find_Root(i)
	lx[rx] = lx.get(rx, 0) + 1
	ry = uf.Find_Root(i + m)
	ly[ry] = ly.get(ry, 0) + 1
for i in range(m * 2):
	ans += lx.get(i, 0) * ly.get(i, 0)
print(ans - n)


def wantp(n):
	q = []
	while p[n] != n:
		q.append(n)
		n = p[n]
	for i in q:
		p[i] = n
	return n
N = int(input())
oXYi = []
XYi = []
YXi = []
for i in range(N):
	(x, y) = map(int, input().split())
	oXYi.append([x, y, i])
	XYi.append([x, y, i])
	YXi.append([y, x, i])
p = [i for i in range(N)]
rank = [1] * N
XYi.sort()
YXi.sort()
li = -1
for i in range(N):
	if XYi[i][0] != XYi[li][0]:
		li = i
	else:
		ti = XYi[i][2]
		tli = XYi[li][2]
		ip = wantp(ti)
		lip = wantp(tli)
		if ip != lip:
			if rank[ip] < rank[lip]:
				p[ip] = lip
			elif rank[ip] > rank[lip]:
				p[lip] = ip
			else:
				p[lip] = ip
				rank[ip] += 1
li = -1
for i in range(N):
	if YXi[i][0] != YXi[li][0]:
		li = i
	else:
		ti = YXi[i][2]
		tli = YXi[li][2]
		ip = wantp(ti)
		lip = wantp(tli)
		if ip != lip:
			if rank[ip] < rank[lip]:
				p[ip] = lip
			elif rank[ip] > rank[lip]:
				p[lip] = ip
			else:
				p[lip] = ip
				rank[ip] += 1
ndic = {}
xkazu = {}
ykazu = {}
for i in range(N):
	np = wantp(i)
	if np not in ndic:
		ndic[np] = 1
	else:
		ndic[np] += 1
	if np not in xkazu:
		xkazu[np] = {}
		ykazu[np] = {}
	xkazu[np][oXYi[i][0]] = 1
	ykazu[np][oXYi[i][1]] = 1
ans = 0
for i in ndic:
	ans += len(xkazu[i]) * len(ykazu[i]) - ndic[i]
print(ans)


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)
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
			willSearch[nxt] = True
			Q.append(nxt)
	ans += xcount * ycount
	ans -= edge_count // 2
print(ans)


import bisect
import heapq
import sys
import queue
input = sys.stdin.readline
sys.setrecursionlimit(100000)

class V:

	def __init__(self, f):
		self.f = f
		self.v = None

	def __str__(self):
		return str(self.v)

	def ud(self, n):
		if self.v is None:
			self.v = n
		else:
			self.v = self.f(self.v, n)

	def get(self):
		return self.v

def read_values():
	return map(int, input().split())

def read_list():
	return list(read_values())

def f(x, X, VX):
	visit_node = [set(), set()]
	visit_link = set()
	q = queue.Queue()
	q.put((0, x))
	while not q.empty():
		(p, s) = q.get()
		if s in visit_node[p]:
			continue
		visit_node[p].add(s)
		VX[p][s] = True
		for t in X[p][s]:
			l = (s, t) if p == 0 else (t, s)
			if l in visit_link:
				continue
			visit_link.add(l)
			if t in visit_node[1 - p]:
				continue
			q.put((1 - p, t))
	return len(visit_node[0]) * len(visit_node[1]) - len(visit_link)

def main():
	N = int(input())
	X = {}
	Y = {}
	for _ in range(N):
		(x, y) = read_values()
		x -= 1
		y -= 1
		X.setdefault(x, set()).add(y)
		Y.setdefault(y, set()).add(x)
	VX = [False] * 10 ** 5
	VY = [False] * 10 ** 5
	res = 0
	for x in X.keys():
		if VX[x]:
			continue
		res += f(x, [X, Y], [VX, VY])
	for y in Y.keys():
		if VY[y]:
			continue
		res += f(y, [Y, X], [VY, VX])
	print(res)
main()


import sys
sys.setrecursionlimit(100000)

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_set = set()
	y_set = set()
	num_e = solve(x, x_set, y_set, X2Y, Y2X)
	ans += len(x_set) * len(y_set) - num_e // 2
print(ans)


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] = []
	xtoy[x].append(y)
	if y not in ytox:
		ytox[y] = []
	ytox[y].append(x)
ret = 0
while len(xtoy) > 0:
	x = next(iter(xtoy))
	xs = set()
	ys = set()
	xs.add(x)
	q = [(x, None)]
	while len(q) > 0:
		(xx, yy) = q.pop(0)
		if xx is not None:
			for y in xtoy.pop(xx):
				if y not in ys:
					ys.add(y)
					q.append((None, y))
		if yy is not None:
			for x in ytox.pop(yy):
				if x not in xs:
					xs.add(x)
					q.append((x, None))
	ret += len(xs) * len(ys)
print(ret - n)


from bisect import bisect_left
N = int(input())
points = list((tuple(map(int, input().split())) for _ in range(N)))
X = sorted(set((x for (x, y) in points)))
Y = sorted(set((y for (x, y) in points)))
points = set(((bisect_left(X, x), bisect_left(Y, y)) for (x, y) in points))
xty = [list() for _ in range(len(X))]
ytx = [list() for _ in range(len(Y))]
for (x, y) in points:
	xty[x].append(y)
	ytx[y].append(x)
area = 0
visited = set()
for p in points:
	if p in visited:
		continue
	stack = [p]
	dx = set()
	dy = set()
	while stack:
		(x, y) = stack.pop()
		visited.add((x, y))
		if x not in dx:
			for yy in xty[x]:
				if yy != y:
					stack.append((x, yy))
			dx.add(x)
		if y not in dy:
			for xx in ytx[y]:
				if xx != x:
					stack.append((xx, y))
			dy.add(y)
	area += len(dx) * len(dy)
print(area - len(points))


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

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()


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)


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)
main()


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):
	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)


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)


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
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):
		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 kruskal(self, edge):
		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()


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

	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 sxy.values():
	y1 = sy.pop()
	for y in sy:
		ufy.unite(y1, y)
for sx in 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)


import sys
input = sys.stdin.readline
import copy
N = int(input())
P = [list(map(int, input().split())) + [i] for i in range(N)]
Q = copy.deepcopy(P)
Group = [i for i in range(N)]

def find(x):
	while Group[x] != x:
		x = Group[x]
	return x

def Union(x, y):
	if find(x) != find(y):
		Group[find(y)] = Group[find(x)] = min(find(y), find(x))
P.sort(key=lambda x: x[0])
for i in range(1, N):
	if P[i][0] == P[i - 1][0]:
		Union(P[i][2], P[i - 1][2])
P.sort(key=lambda x: x[1])
for i in range(1, N):
	if P[i][1] == P[i - 1][1]:
		Union(P[i][2], P[i - 1][2])
B = [[] for i in range(N)]
for i in range(N):
	B[find(i)].append(i)
ANS = 0
for BEL in B:
	X = []
	Y = []
	for b in BEL:
		X.append(Q[b][0])
		Y.append(Q[b][1])
	ANS += len(set(X)) * len(set(Y)) - len(BEL)
print(ANS)


import sys
sys.setrecursionlimit(10 ** 7)
from itertools import accumulate, combinations, permutations
from math import factorial

def factorize(n):
	fct = []
	(b, e) = (2, 0)
	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):
	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):
	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
from heapq import heapify, heappop, heappush, heappushpop, heapreplace, nlargest, nsmallest
from copy import deepcopy, copy
from operator import itemgetter
from functools import reduce
from fractions import gcd

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)
INF = 10 ** 18
MOD = 10 ** 9 + 7
modpow = lambda a, n, p=MOD: pow(a, n, p)

def modinv(a, p=MOD):
	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):
	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))
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()))
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)
__starting_point()


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:

		def __init__(self, size):
			self.size = size
			self.tree_root = list(range(self.size))
			self.tree_depth = [1] * self.size

		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()


import sys, math
from collections import defaultdict, deque, Counter
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)
ans = 0
visited = {}
for x in X:
	if x in visited:
		continue
	(nx, ny) = (0, 0)
	queue = deque()
	queue.append(x)
	visited[x] = 1
	while queue:
		n = queue.pop()
		if n < V:
			nx += 1
		else:
			ny += 1
		for m in to[n]:
			if m in visited:
				continue
			visited[m] = 1
			queue.append(m)
	ans += nx * ny
print(ans - N)


n = int(input())
xy = [list(map(int, input().split())) for _ in range(n)]
mx = 0
my = 0
for i in range(n):
	mx = max(mx, xy[i][0])
	my = max(my, xy[i][1])
(*p,) = range(mx + my + 1)
rank = [1] * (mx + my + 1)
for i in range(n):
	xy[i][1] += mx

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
	rx = rank[px]
	ry = rank[py]
	if ry < rx:
		p[py] = px
	elif rx < ry:
		p[px] = py
	else:
		p[py] = px
		rank[px] += 1
	return 1
for (x, y) in xy:
	unite(x, y)
xp = [0] * (mx + my + 1)
yp = [0] * (mx + my + 1)
for i in range(mx + my + 1):
	if i <= mx:
		xp[root(i)] += 1
	else:
		yp[root(i)] += 1
ans = 0
for i in range(mx + my + 1):
	ans += xp[i] * yp[i]
ans -= n
print(ans)


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)


N = int(input())
(x, y) = zip(*(map(int, input().split()) for _ in range(N))) if N else ((), ())

class UnionFindTree:

	def __init__(self, n):
		self.p = [i for i in range(n + 1)]
		self.r = [1 for _ in range(n + 1)]

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

	def union(self, x, y):
		px = self.find(x)
		py = self.find(y)
		if px != py:
			if self.r[px] < self.r[py]:
				self.p[px] = py
				self.r[py] += self.r[px]
			else:
				self.p[py] = px
				self.r[px] += self.r[py]

	def same(self, x, y):
		return self.find(x) == self.find(y)
uft = UnionFindTree(10 ** 5)
X = [[] for _ in range(10 ** 5 + 1)]
for (a, b) in zip(x, y):
	X[a].append(b)
Y = [[] for j in range(10 ** 5 + 1)]
for (a, b) in zip(x, y):
	Y[b].append(a)
SX = set(x)
SY = set(y)
for j in SY:
	for i in Y[j]:
		uft.union(Y[j][0], i)
X2 = [[] for _ in range(10 ** 5 + 1)]
for i in SX:
	X2[uft.find(i)].append(i)
ans = 0
for i in SX:
	S = set()
	for i2 in X2[i]:
		for j in X[i2]:
			S.add(j)
	ans += len(X2[i]) * len(S)
ans -= N
print(ans)


class UnionFind(object):

	def __init__(self, group_id, x_zahyou, y_zahyou, parent=None, value=None):
		self.group_id_ = group_id
		self.x_zahyou = x_zahyou
		self.y_zahyou = y_zahyou
		self.parent_ = parent
		self.value = value
		self.rank_ = 1

	def __str__(self):
		template = 'UnionFindNode(group_id: {}, \n\tparent: {}, value: {}, size: {})'
		return template.format(self.group_id_, self.parent_, self.value, self.rank_)

	def is_root(self):
		return not self.parent_

	def root(self):
		parent = self
		while not parent.is_root():
			parent = parent.parent_
			self.parent_ = parent
		return parent

	def find(self):
		root = self.root()
		return root.group_id_

	def rank(self):
		root = self.root()
		return root.rank_

	def unite(self, unite_node):
		root = self.root()
		unite_root = unite_node.root()
		if root.group_id_ != unite_root.group_id_:
			if root.rank() > unite_root.rank():
				unite_root.parent_ = root
				root.rank_ = max(root.rank_, unite_root.rank_ + 1)
				root.x_zahyou += unite_root.x_zahyou
				root.y_zahyou += unite_root.y_zahyou
			else:
				root.parent_ = unite_root
				unite_root.rank_ = max(root.rank_ + 1, unite_root.rank_)
				unite_root.x_zahyou += root.x_zahyou
				unite_root.y_zahyou += root.y_zahyou
N = int(input())
X = []
Y = []
node_list = []
for i in range(N):
	(x, y) = map(int, input().split())
	X += [[i, x]]
	Y += [[i, y]]
	node_list += [UnionFind(i, [x], [y])]
X.sort(key=lambda x: x[1])
Y.sort(key=lambda x: x[1])
for i in range(N - 1):
	if X[i][1] == X[i + 1][1]:
		node_list[X[i][0]].unite(node_list[X[i + 1][0]])
	if Y[i][1] == Y[i + 1][1]:
		node_list[Y[i][0]].unite(node_list[Y[i + 1][0]])
chouten = 0
for i in range(N):
	if node_list[i].is_root():
		chouten += len(set(node_list[i].x_zahyou)) * len(set(node_list[i].y_zahyou))
print(chouten - N)


import sys
input = sys.stdin.buffer.readline
from collections import defaultdict

def getlist():
	return list(map(int, input().split()))

class UnionFind:

	def __init__(self, n):
		self.par = [i for i in range(n)]
		self.rank = [0] * n
		self.size = [1] * 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 same_check(self, x, y):
		return self.find(x) == self.find(y)

	def union(self, x, y):
		x = self.find(x)
		y = self.find(y)
		if self.rank[x] < self.rank[y]:
			if self.same_check(x, y) != True:
				self.size[y] += self.size[x]
				self.size[x] = 0
			self.par[x] = y
		else:
			if self.same_check(x, y) != True:
				self.size[x] += self.size[y]
				self.size[y] = 0
			self.par[y] = x
			if self.rank[x] == self.rank[y]:
				self.rank[x] += 1

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

def main():
	N = int(input())
	Lx = []
	Ly = []
	UF = UnionFind(N)
	for i in range(N):
		(x, y) = getlist()
		Lx.append([x, y, i])
		Ly.append([y, x, i])
	Lx = sorted(Lx)
	Ly = sorted(Ly)
	for i in range(N - 1):
		if Lx[i][0] == Lx[i + 1][0]:
			UF.union(Lx[i][2], Lx[i + 1][2])
		if Ly[i][0] == Ly[i + 1][0]:
			UF.union(Ly[i][2], Ly[i + 1][2])
	for i in range(N):
		UF.par[i] = UF.find(i)
	nodeX = [[] for i in range(N)]
	nodeY = [[] for i in range(N)]
	PAR = UF.par
	for i in range(N):
		nodeX[PAR[Lx[i][2]]].append(Lx[i][0])
		nodeY[PAR[Lx[i][2]]].append(Lx[i][1])
	ans = 0
	D = defaultdict(int)
	for i in range(N):
		if D[PAR[i]] == 0:
			D[PAR[i]] = 1
			ans += len(set(nodeX[PAR[i]])) * len(set(nodeY[PAR[i]])) - UF.size[PAR[i]]
	print(ans)
main()


from collections import defaultdict
n = int(input())
dx = defaultdict(list)
dy = defaultdict(list)
pts = set()
for _ in range(n):
	(x, y) = map(int, input().split())
	dx[x].append(y)
	dy[y].append(x)
	pts.add((x, y))
(vx, vy) = (set(), set())
cnt = 0
for (x, y) in pts:
	if x in vx or y in vy:
		continue
	(sx, sy) = ([x], [y])
	(xs, ys) = (set(), set())
	while sx or sy:
		while sx:
			x = sx.pop()
			if x not in xs:
				xs.add(x)
				for y in dx[x]:
					if y not in ys:
						sy.append(y)
		while sy:
			y = sy.pop()
			if y not in ys:
				ys.add(y)
				for x in dy[y]:
					if x not in xs:
						sx.append(x)
	vx.update(xs)
	vy.update(ys)
	cnt += len(xs) * len(ys)
print(cnt - len(pts))


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

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)


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)


from collections import defaultdict
N = int(input())
d = defaultdict(list)
cd = set()
D = 100000
used = [0] * (2 * D + 1)
ans = 0
for _ in range(N):
	(x, y) = map(int, input().split())
	y += D
	cd.add(x)
	d[x].append(y)
	d[y].append(x)
for x in cd:
	if used[x]:
		continue
	q = [x]
	used[x] = 1
	(cx, sx) = (0, 0)
	while q:
		a = q.pop()
		if a <= D:
			cx += 1
		sx += 1
		for b in d[a]:
			if used[b]:
				continue
			used[b] = 1
			q.append(b)
	ans += cx * (sx - cx)
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)


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()
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)


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

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())
sys.setrecursionlimit(100000)

class UnionFind:

	def __init__(self, n):
		self.data = [-1] * n

	def unite(self, 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)
		self.data[x] += self.data[y]
		self.data[y] = x

	def root(self, 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):
		return self.root(x) == self.root(y)

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

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])

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)

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():
	ABC131_F()
__starting_point()


def main():
	import sys
	input = sys.stdin.readline
	sys.setrecursionlimit(10 ** 7)
	from collections import Counter, deque
	from itertools import combinations, permutations, accumulate, groupby, product
	from bisect import bisect_left, bisect_right
	from heapq import heapify, heappop, heappush
	import math
	n = int(input())

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

	def unite(x, y):
		x = find(x)
		y = find(y)
		if x == y:
			return False
		else:
			if par[x] > par[y]:
				(x, y) = (y, x)
			par[x] += par[y]
			par[y] = x
			return True

	def size(x):
		return -par[find(x)]
	par = [-1] * (2 * 10 ** 5 + 2)
	s = set()
	for _ in range(n):
		(x, y) = map(int, input().split())
		unite(x, 10 ** 5 + y)
		s.add(x)
	cnt = Counter()
	for x in s:
		cnt[find(x)] += 1
	res = 0
	for (k, v) in cnt.items():
		res += v * (size(k) - v)
	print(res - n)
main()


import sys

def dfs(s, links, dots, checked):
	q = [s]
	xs = set()
	ys = set()
	cnt = 0
	while q:
		v = q.pop()
		if checked[v]:
			continue
		checked[v] = True
		(x, y) = dots[v]
		xs.add(x)
		ys.add(y)
		cnt += 1
		q.extend(links[v])
	return max(0, len(xs) * len(ys) - cnt)
n = int(input())
dots = [tuple(map(int, line.split())) for line in sys.stdin]
xs = {}
ys = {}
links = [set() for _ in [0] * n]
for (i, (x, y)) in enumerate(dots):
	if x in xs:
		links[i].add(xs[x])
		links[xs[x]].add(i)
	else:
		xs[x] = i
	if y in ys:
		links[i].add(ys[y])
		links[ys[y]].add(i)
	else:
		ys[y] = i
checked = [False] * n
ans = 0
for i in range(n):
	if checked[i]:
		continue
	ans += dfs(i, links, dots, checked)
print(ans)


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))
(_, components) = connected_components(graph)
x_cnt = np.bincount(components[:PAD], minlength=PAD * 2)
y_cnt = np.bincount(components[PAD:], minlength=PAD * 2)
print((x_cnt * y_cnt).sum() - N)


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)


n = int(input())
m = 10 ** 5
edge = [[] for i in range(2 * m)]
for i in range(n):
	(x, y) = map(int, input().split())
	x -= 1
	y -= 1
	y += m
	edge[x].append(y)
	edge[y].append(x)
used = [False] * 2 * m
res = 0
for e in range(m):
	if used[e]:
		continue
	used[e] = True
	tank = []
	new = [e]
	px = 0
	py = 0
	if e >= 10 ** 5:
		py += 1
	else:
		px += 1
	while len(new) > 0:
		tank = []
		for fr in new:
			for go in edge[fr]:
				if not used[go]:
					tank.append(go)
					used[go] = True
					if go >= 10 ** 5:
						py += 1
					else:
						px += 1
		new = tank
	res += px * py
print(res - n)


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:

	def __init__(self, n):
		self.n = n
		self.par = [i for i in range(n + 1)]
		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]
			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))
			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)
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()):
	ans += xcnt * ycnt - edgecnt
print(ans)


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)))


import sys
input = sys.stdin.readline
N = int(input())
XY = [[set() for i in range(10 ** 5 + 1)] for j in range(2)]
for i in range(N):
	(x, y) = map(int, input().split())
	XY[0][x].add(y)
	XY[1][y].add(x)
is_visited_x = [False] * (10 ** 5 + 1)

def dfs(x):
	global is_visited_x
	is_visited_x[x] = True
	stack = [[x], []]
	idx = 0
	num_e = 0
	num_xy = [1, 0]
	parent = 0
	while stack[0] or stack[1]:
		node = stack[idx][-1]
		children = XY[idx][node]
		if children:
			child = children.pop()
			XY[idx - 1][child].remove(node)
			num_e += 1
			if child in stack[idx - 1]:
				continue
			stack[idx - 1].append(child)
			num_xy[idx - 1] += 1
			is_visited_x[child] += idx
			idx = (idx + 1) % 2
		else:
			stack[idx].pop()
			idx = (idx + 1) % 2
	return (num_e, num_xy)
ans = 0
for x in range(1, 10 ** 5 + 1):
	if is_visited_x[x]:
		continue
	(num_e, num_xy) = dfs(x)
	ans += num_xy[0] * num_xy[1] - num_e
print(ans)


N = int(input())
Points = [list(map(int, input().split())) for i in range(N)]
X_dict = {x: [] for x in range(1, 10 ** 5 + 1)}
Y_dict = {y: [] for y in range(1, 10 ** 5 + 1)}
for (i, (x, y)) in enumerate(Points):
	X_dict[x].append((y, i))
	Y_dict[y].append((x, i))
ans = 0
Visited = [False] * N
for (i, (x, y)) in enumerate(Points):
	if Visited[i]:
		continue
	stack = [('x', x), ('y', y)]
	cnt = 1
	(x_set, y_set) = ({x}, {y})
	Visited[i] = True
	while stack:
		(s, v) = stack.pop()
		if s == 'x':
			for (yd, j) in X_dict[v]:
				if Visited[j]:
					continue
				else:
					cnt += 1
					y_set.add(yd)
					stack.append(('y', yd))
					Visited[j] = True
		else:
			for (xd, j) in Y_dict[v]:
				if Visited[j]:
					continue
				else:
					cnt += 1
					x_set.add(xd)
					stack.append(('x', xd))
					Visited[j] = True
	ans += len(x_set) * len(y_set) - cnt
print(ans)


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):
		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) 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()}
((n,), *q) = [[*map(int, o.split())] for o in open(0)]
MAX = 10 ** 5 + 1
UF = UnionFind(2 * MAX)
for (x, y) in q:
	UF.union(x, y + MAX)
cnt_x = [0] * (2 * MAX)
cnt_y = cnt_x[:]
for i in range(MAX):
	cnt_x[UF.find(i)] += 1
	cnt_y[UF.find(i + MAX)] += 1
print(sum((X * Y for (X, Y) in zip(cnt_x, cnt_y))) - n)


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)


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

def main():
	N = int(input())
	V = 10 ** 5 + 5
	edge = [[] * (2 * V) for _ in range(2 * V)]
	visited = [False] * (2 * V)
	for i in range(N):
		(x, y) = map(int, input().split())
		y += V
		edge[x].append(y)
		edge[y].append(x)
	ans = 0
	for i in range(2 * V):
		if visited[i]:
			continue
		cnt = [0, 0]
		q = deque([i])
		while q:
			s = q.pop()
			if visited[s]:
				continue
			visited[s] = True
			cnt[s // V] += 1
			for v in edge[s]:
				if visited[v]:
					continue
				q.append(v)
		ans += cnt[0] * cnt[1]
	print(ans - N)
main()


import sys
from collections import defaultdict
from collections import deque

def solve(N: int, X: 'List[int]', Y: 'List[int]'):
	adj = defaultdict(list)
	done = set()
	for (x, y) in zip(X, Y):
		xnode = (x, 0)
		ynode = (y, 1)
		adj[xnode].append(ynode)
		adj[ynode].append(xnode)
	points_to_add = 0
	for (x, y) in zip(X, Y):
		xnode = (x, 0)
		ynode = (y, 1)
		if xnode in done or ynode in done:
			continue
		x_count = 1
		y_count = 0
		done.add(xnode)
		Q = deque()
		Q.append(xnode)
		while len(Q) > 0:
			node = Q.pop()
			for node_next in adj[node]:
				if node_next in done:
					continue
				if node_next[1] == 0:
					x_count += 1
				else:
					y_count += 1
				Q.append(node_next)
				done.add(node_next)
		points_to_add += x_count * y_count
	print(points_to_add - N)
	return

def main():

	def iterate_tokens():
		for line in sys.stdin:
			for word in line.split():
				yield word
	tokens = iterate_tokens()
	N = int(next(tokens))
	x = [int()] * N
	y = [int()] * N
	for i in range(N):
		x[i] = int(next(tokens))
		y[i] = int(next(tokens))
	solve(N, x, y)
main()


from collections import deque
N = int(input())
nmax = 2 * 10 ** 5
nhalf = 10 ** 5
G = [[] for i in range(nmax)]
for i in range(N):
	(x, y) = map(int, input().split())
	G[x - 1].append(nhalf + y - 1)
	G[nhalf + y - 1].append(x - 1)
used = [False] * nmax
ans = 0
for i in range(nhalf):
	if G[i] != [] and (not used[i]):
		cnt_1 = 0
		cnt_2 = 0
		q = deque([i])
		used[i] = True
		while len(q) > 0:
			a = q.popleft()
			if a < nhalf:
				cnt_2 += 1
			else:
				cnt_1 += 1
			Vs = G[a]
			for u in Vs:
				if not used[u]:
					q.append(u)
					used[u] = True
		ans += cnt_1 * cnt_2
print(ans - N)


from collections import defaultdict, deque, Counter
from heapq import heappush, heappop, heapify
import math
from bisect import bisect_left, bisect_right
import random
from itertools import permutations, accumulate, combinations
import sys
import string
INF = float('inf')

def LI():
	return list(map(int, sys.stdin.readline().split()))

def I():
	return int(sys.stdin.readline())

def LS():
	return sys.stdin.readline().split()

def S():
	return sys.stdin.readline().strip()

def IR(n):
	return [I() for i in range(n)]

def LIR(n):
	return [LI() for i in range(n)]

def SR(n):
	return [S() for i in range(n)]

def LSR(n):
	return [LS() for i in range(n)]

def SRL(n):
	return [list(S()) for i in range(n)]

def MSRL(n):
	return [[int(j) for j in list(S())] for i in range(n)]
mod = 10 ** 9 + 7

class UnionFind:

	def __init__(self, n):
		self.table = [-1] * n
		self.size = [1] * n
		self.group_num = n

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

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

	def get_size(self, x):
		r = self.root(x)
		return self.size[r]

	def union(self, x, y):
		r1 = self.root(x)
		r2 = self.root(y)
		if r1 == r2:
			return
		d1 = self.table[r1]
		d2 = self.table[r2]
		if d1 <= d2:
			self.table[r2] = r1
			self.size[r1] += self.size[r2]
			if d1 == d2:
				self.table[r1] -= 1
		else:
			self.table[r1] = r2
			self.size[r2] += self.size[r1]
		self.group_num -= 1
n = I()
U = UnionFind(10 ** 5 * 2)
for i in range(n):
	(x, y) = LI()
	U.union(x - 1, y + 10 ** 5 - 1)
X = [0] * 10 ** 5 * 2
Y = [0] * 10 ** 5 * 2
for x in range(10 ** 5):
	X[U.root(x)] += 1
for y in range(10 ** 5, 10 ** 5 * 2):
	Y[U.root(y)] += 1
ret = 0
for j in range(10 ** 5 * 2):
	ret += X[j] * Y[j]
print(ret - n)


import bisect
import copy
import heapq
import math
import sys
from collections import *
from functools import lru_cache
from itertools import accumulate, combinations, permutations, product

def input():
	return sys.stdin.readline()[:-1]

def ruiseki(lst):
	return [0] + list(accumulate(lst))
sys.setrecursionlimit(5000000)
mod = pow(10, 9) + 7
al = [chr(ord('a') + i) for i in range(26)]
direction = [[1, 0], [0, 1], [-1, 0], [0, -1]]
n = int(input())
xy = [list(map(int, input().split())) for i in range(n)]
dic = {}
for i in range(n):
	(x, y) = xy[i]
	if (0, x) in dic:
		dic[0, x].append(y)
	else:
		dic[0, x] = [y]
	(x, y) = (y, x)
	if (1, x) in dic:
		dic[1, x].append(y)
	else:
		dic[1, x] = [y]
lst = [[0] * (10 ** 5 + 1) for i in range(2)]
ans = 0
for (x, y) in xy:
	if lst[0][x] == 1:
		continue
	lst[0][x] = 1
	d = deque([(0, x)])
	(xcnt, ycnt) = (0, 0)
	cnt = 0
	while d:
		now = d.popleft()
		if now[0] == 0:
			xcnt += 1
		else:
			ycnt += 1
		for j in dic[now]:
			cnt += 1
			if lst[1 - now[0]][j] == 0:
				lst[1 - now[0]][j] = 1
				d.append((1 - now[0], j))
	ans += xcnt * ycnt - cnt // 2
print(ans)


from collections import deque
N = int(input())
mod = 100010
visit = [0] * (2 * mod)
table = [[] for i in range(2 * mod)]
for i in range(N):
	(x, y) = map(int, input().split())
	y += mod
	visit[x] = 1
	visit[y] = 1
	table[x].append(y)
	table[y].append(x)
H = deque()
num = 0
for i in range(mod):
	if visit[i] == 1:
		X = 0
		Y = 0
		H.append(i)
		visit[i] = 0
		while H:
			pt = H.popleft()
			if pt > mod:
				Y += 1
			else:
				X += 1
			for x in table[pt]:
				if visit[x] == 1:
					visit[x] = 0
					H.append(x)
		num += X * Y
print(num - N)