import sys

# DFSの再帰限界を増やす

sys.setrecursionlimit(10**6)



N = int(eval(input()))

nmax = 100001

# nmax = 10

# 第一成分にx,第二成分にyをぶち込んでいく。(二部グラフなので)

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 = list(map(int, input().split()))

    xys[0][x].append(y)

    xys[1][y].append(x)

# print(xys)



# 座標の切り替えをx = 1 or 0と1-xで行うのか、うまい！

# よく考えたらtrue/falseをひっくり返しているだけか。

def dfs(i, el=0):

    # el=0->x, el=1->y

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

from collections import deque



N = int(eval(input()))

nmax = 2*10**5

nhalf = 10**5



G = [[] for i in range(nmax)]

for i in range(N):

    x,y = list(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):

    # 見つかったノードからBFS

    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]

            # print(Vs)

            for u in Vs: # 頂点以外の要素がグラフにあるときはここ

                if not used[u]:

                    q.append(u)

                    # なにか処理を書きたいときはここに書く

                    used[u] = True

        ans += cnt_1*cnt_2

print((ans-N))
import sys

# DFSの再帰限界を増やす
sys.setrecursionlimit(10**6)
N = int(eval(input()))
nmax = 100001
# nmax = 10
# 第一成分にx,第二成分にyをぶち込んでいく。(二部グラフなので)
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 = list(map(int, input().split()))
    xys[0][x].append(y)
    xys[1][y].append(x)
# print(xys)
# 座標の切り替えをx = 1 or 0と1-xで行うのか、うまい！
# よく考えたらtrue/falseをひっくり返しているだけか。
def dfs(i, el=0):
    # el=0->x, el=1->y
    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))

from collections import deque

N = int(eval(input()))
nmax = 2 * 10**5
nhalf = 10**5
G = [[] for i in range(nmax)]
for i in range(N):
    x, y = list(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):
    # 見つかったノードからBFS
    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]
            # print(Vs)
            for u in Vs:  # 頂点以外の要素がグラフにあるときはここ
                if not used[u]:
                    q.append(u)
                    # なにか処理を書きたいときはここに書く
                    used[u] = True
        ans += cnt_1 * cnt_2
print((ans - N))

-import sys +from collections import deque -# DFSの再帰限界を増やす -sys.setrecursionlimit(10**6) -nmax = 100001 -# nmax = 10 -# 第一成分にx,第二成分にyをぶち込んでいく。(二部グラフなので) -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): +nmax = 2 * 10**5 +nhalf = 10**5 +G = [[] for i in range(nmax)] +for i in range(N): -    xys[0][x].append(y) -    xys[1][y].append(x) -# print(xys) -# 座標の切り替えをx = 1 or 0と1-xで行うのか、うまい！ -# よく考えたらtrue/falseをひっくり返しているだけか。 -def dfs(i, el=0): -    # el=0->x, el=1->y -    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 - - +    G[x - 1].append(nhalf + y - 1) +    G[nhalf + y - 1].append(x - 1) +used = [False] * nmax -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] +# 全探索 +for i in range(nhalf): +    # 見つかったノードからBFS +    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] +            # print(Vs) +            for u in Vs:  # 頂点以外の要素がグラフにあるときはここ +                if not used[u]: +                    q.append(u) +                    # なにか処理を書きたいときはここに書く +                    used[u] = True +        ans += cnt_1 * cnt_2