There are N dots in a two-dimensional plane. The coordinates of the i-th dot are (x_i, y_i).
We will repeat the following operation as long as possible:
 - Choose four integers a, b, c, d (a \neq c, b \neq d) such that there are dots at exactly three of the positions (a, b), (a, d), (c, b) and (c, d), and add a dot at the remaining position.
We can prove that we can only do this operation a finite number of times. Find the maximum number of times we can do the operation.

-----Constraints-----
 - 1 \leq N \leq 10^5
 - 1 \leq x_i, y_i \leq 10^5
 - If i \neq j, x_i \neq x_j or y_i \neq y_j.
 - All values in input are integers.

-----Input-----
Input is given from Standard Input in the following format:
N
x_1 y_1
:
x_N y_N

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

-----Sample Input-----
3
1 1
5 1
5 5

-----Sample Output-----
1

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

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


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)


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)


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


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)


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


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)


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)


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)


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)


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


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


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


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)


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


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)


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


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)


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)