# coding: utf-8

# Your code here!



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): #root(x): 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): #merge(x,y): 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] #yの要素数を更新

            self.parent[x] = y #xをyにつなぐ

            self.xval[y].update(self.xval[x])

            self.yval[y].update(self.yval[x])

        else:

            self.size[x] += self.size[y] #xの要素数を更新

            self.parent[y] = x #yをxにつなぐ

            self.xval[x].update(self.xval[y])

            self.yval[x].update(self.yval[y])

        return True

 

    def issame(self, x, y): #same(x,y): xとyが同じグループにあるならTrue

        return self.root(x) == self.root(y)

        

    def size(self,x): #size(x): xのいるグループの要素数を返す

        return self.size[self.root(x)]



n = int(eval(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(xval,yval)



print(ans)









# coding: utf-8

# Your code here!



class UnionFind:

    def __init__(self, n):

        self.parent = list(range(n))#親ノード

        self.size = [1]*n #グループの要素数



    def root(self, x): #root(x): 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): #merge(x,y): 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] #yの要素数を更新

            self.parent[x] = y #xをyにつなぐ

        else:

            self.size[x] += self.size[y] #xの要素数を更新

            self.parent[y] = x #yをxにつなぐ

        return True

 

    def issame(self, x, y): #same(x,y): xとyが同じグループにあるならTrue

        return self.root(x) == self.root(y)

        

    def size(self,x): #size(x): xのいるグループの要素数を返す

        return self.size[self.root(x)]



n = int(eval(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

#print(xcon,ycon)

for key, val in list(xcon.items()):

    ans += len(val) * len(ycon[key])



#print(UF.parent)

#print(xval,yval)



print(ans)











# coding: utf-8
# Your code here!
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):  # root(x): 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):  # merge(x,y): 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]  # yの要素数を更新
            self.parent[x] = y  # xをyにつなぐ
            self.xval[y].update(self.xval[x])
            self.yval[y].update(self.yval[x])
        else:
            self.size[x] += self.size[y]  # xの要素数を更新
            self.parent[y] = x  # yをxにつなぐ
            self.xval[x].update(self.xval[y])
            self.yval[x].update(self.yval[y])
        return True

    def issame(self, x, y):  # same(x,y): xとyが同じグループにあるならTrue
        return self.root(x) == self.root(y)

    def size(self, x):  # size(x): xのいるグループの要素数を返す
        return self.size[self.root(x)]


n = int(eval(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(xval,yval)
print(ans)

# coding: utf-8
# Your code here!
class UnionFind:
    def __init__(self, n):
        self.parent = list(range(n))  # 親ノード
        self.size = [1] * n  # グループの要素数

    def root(self, x):  # root(x): 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):  # merge(x,y): 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]  # yの要素数を更新
            self.parent[x] = y  # xをyにつなぐ
        else:
            self.size[x] += self.size[y]  # xの要素数を更新
            self.parent[y] = x  # yをxにつなぐ
        return True

    def issame(self, x, y):  # same(x,y): xとyが同じグループにあるならTrue
        return self.root(x) == self.root(y)

    def size(self, x):  # size(x): xのいるグループの要素数を返す
        return self.size[self.root(x)]


n = int(eval(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
# print(xcon,ycon)
for key, val in list(xcon.items()):
    ans += len(val) * len(ycon[key])
# print(UF.parent)
# print(xval,yval)
print(ans)

-    def __init__(self, n, xy): +    def __init__(self, n): -        self.xval = [{x} for x, y in xy] -        self.yval = [{y} for x, y in xy] -            self.xval[y].update(self.xval[x]) -            self.yval[y].update(self.yval[x]) -            self.xval[x].update(self.xval[y]) -            self.yval[x].update(self.yval[y]) -UF = UnionFind(n, xy) +UF = UnionFind(n) +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} -for i, pi in enumerate(UF.parent): -    if i == pi: -        ans += len(UF.xval[pi]) * len(UF.yval[pi]) +# print(xcon,ycon) +for key, val in list(xcon.items()): +    ans += len(val) * len(ycon[key]) +# print(UF.parent)