Problem:
 Score : 600 points    Problem Statement 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 1 3
1 1
5 1
5 5      Sample Output 1 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 .       Sample Input 2 2
10 10
20 20      Sample Output 2 0  There are only two dots, so we cannot do the operation at all.       Sample Input 3 9
1 1
2 1
3 1
4 1
5 1
1 2
1 3
1 4
1 5      Sample Output 3 16  We can do the operation for all choices of the form a = 1 , b = 1 , c = i , d = j  (2 \leq i,j \leq 5) , and no more. Thus, the maximum number of operations is 16 .

Solution (Python):
import os
import sys
from collections import defaultdict

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

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


class UnionFind:
    def __init__(self, size=None, nodes=None):
        """
        size か nodes どっちか指定。
        nodes は set、size は list を使う。
        set の最悪計算量は O(N) なので size 指定のほうが若干速い
        :param int size:
        :param collections.Iterable nodes:
        """
        assert size is not None or nodes is not None
        if size is not None:
            self._parents = [i for i in range(size)]
            self._ranks = [0 for _ in range(size)]
            self._sizes = [1 for _ in range(size)]
        else:
            self._parents = {k: k for k in nodes}
            self._ranks = {k: 0 for k in nodes}
            self._sizes = {k: 1 for k in nodes}

    def unite(self, x, y):
        """
        x が属する木と y が属する木を併合
        :param x:
        :param y:
        :return:
        """
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return

        
        if self._ranks[x] > self._ranks[y]:
            
            self._parents[y] = x
            self._sizes[x] += self._sizes[y]
        else:
            
            self._parents[x] = y
            self._sizes[y] += self._sizes[x]
            if self._ranks[x] == self._ranks[y]:
                self._ranks[y] += 1

    def find(self, x):
        """
        x が属する木の root
        :param x:
        :return:
        """
        if self._parents[x] == x:
            return x
        self._parents[x] = self.find(self._parents[x])
        return self._parents[x]

    def size(self, x):
        """
        x が属する木のノード数
        :param x:
        :return:
        """
        return self._sizes[self.find(x)]


class WeightedUnionFind:
    def __init__(self, size=None, nodes=None):
        """
        size か nodes どっちか指定。
        nodes は set、size は list を使う。
        set の最悪計算量は O(N) なので size 指定のほうが若干速い
        :param int size:
        :param collections.Iterable nodes:
        """
        assert size is not None or nodes is not None
        if size is not None:
            self._parents = [i for i in range(size)]
            self._ranks = [0 for _ in range(size)]
            self._sizes = [1 for _ in range(size)]
            
            self._weights = [0 for _ in range(size)]
        else:
            self._parents = {k: k for k in nodes}
            self._ranks = {k: 0 for k in nodes}
            self._sizes = {k: 1 for k in nodes}
            
            self._weights = {k: 0 for k in nodes}

    def unite(self, x, y, w):
        """
        x が属する木と y が属する木を併合
        :param x:
        :param y:
        :param w: x と y の重みの差; (重み y) - (重み x)
        :return:
        """
        rx = self.find(x)
        ry = self.find(y)
        if rx == ry:
            return

        
        if self._ranks[rx] > self._ranks[ry]:
            
            self._parents[ry] = rx
            self._sizes[rx] += self._sizes[ry]
            
            self._weights[ry] = w + self._weights[x] - self._weights[y]
        else:
            
            self._parents[rx] = ry
            self._sizes[ry] += self._sizes[rx]
            
            self._weights[rx] = -w + self._weights[y] - self._weights[x]
            if self._ranks[rx] == self._ranks[ry]:
                self._ranks[ry] += 1

    def find(self, x):
        """
        x が属する木の root
        :param x:
        :return:
        """
        if self._parents[x] == x:
            return x
        root = self.find(self._parents[x])
        self._weights[x] += self._weights[self._parents[x]]
        self._parents[x] = root
        return root

    def size(self, x):
        """
        x が属する木のノード数
        :param x:
        :return:
        """
        return self._sizes[self.find(x)]

    def weight(self, x):
        """
        :param x:
        :return:
        """
        
        self.find(x)
        return self._weights[x]

    def diff(self, x, y):
        """
        (y の重み) - (x の重み)
        :param x:
        :param y:
        :return:
        """
        if self.find(x) == self.find(y):
            return self._weights[y] - self._weights[x]
        return float("inf")


N = int(sys.stdin.readline())
X, Y = list(zip(*[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)


uf = UnionFind(size=10 ** 5 + 1)
for ys in xy.values():
    yli = list(ys)
    for y1, y2 in zip(yli[1:], yli[:-1]):
        uf.unite(y1, y2)

yx2 = defaultdict(lambda: set())
for y, xs in yx.items():
    root = uf.find(y)
    yx2[root] |= xs

ans = 0
for y, xs in yx2.items():
    ans += uf.size(y) * len(xs)
ans -= N
print(ans)

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

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

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

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

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

Solution (Python):
get = lambda: int(raw_input().strip())
gets = lambda: map(int, raw_input().strip().split())
getss = lambda n: [gets() for _ in xrange(n)]








N = get()
A = getss(N)
UF = {}


def find(x):
    if x != UF[x]:
        UF[x] = find(UF[x])
    return UF[x]


def union(x, y):
    UF.setdefault(x, x)
    UF.setdefault(y, y)
    UF[find(x)] = find(y)


for i, j in A:
    union(i, -j)
count = {}
for x in UF:
    root = find(x)
    if root not in count:
        count[root] = [0, 0]
    count[root][x > 0] += 1

print sum(u[0] * u[1] for u in count.values()) - N

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

Solution (Python):
from collections import defaultdict
n = int(input())
dx = defaultdict(list)
dy = defaultdict(list)
pts = set()
for _ in range(n):
    x, y = map(int, input().split())
    dx[x].append(y)
    dy[y].append(x)
    pts.add((x, y))

vx, vy = set(), set()
cnt = 0
for x, y in pts:
    if x in vx or y in vy: continue
    sx, sy = [x], [y]
    xs, ys = set(), set()
    while sx or sy:
        while sx:
            x = sx.pop()
            if x not in xs:
                xs.add(x)
                for y in dx[x]:
                    if y not in ys:
                        sy.append(y)
        while sy:
            y = sy.pop()
            if y not in ys:
                ys.add(y)
                for x in dy[y]:
                    if x not in xs:
                        sx.append(x)
    vx.update(xs)
    vy.update(ys)
    cnt += len(xs) * len(ys)
print(cnt - len(pts))

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

Solution (Python):
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 != gy and gx != 0 and gy != 0:  
            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 = 0
        for xy in xys:
            if xy > 0:
                nx += 1
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):
def f_must_be_rectangular(N, Positions):
    
    
    import sys
    sys.setrecursionlimit(10**6)

    vertex_maxsize = 10**5 + 1
    target = [[] for _ in range(2 * vertex_maxsize)]
    is_visited = [False] * (2 * vertex_maxsize)
    for x, y in Positions:
        y += vertex_maxsize  
        target[x].append(y)
        target[y].append(x)

    def dfs(v):
        if is_visited[v]:
            return
        is_visited[v] = True
        count[v // vertex_maxsize] += 1  
        for u in target[v]:
            dfs(u)  

    ans = 0
    for i in range(2 * vertex_maxsize):
        if is_visited[i]:
            continue
        count = [0] * 2
        dfs(i)  
        
        
        ans += count[0] * count[1]
    return ans - N  

N = int(input())
Positions = [[int(i) for i in input().split()] for j in range(N)]
print(f_must_be_rectangular(N, Positions))

Solution (Python):
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 or gy == 0:
            g = gx or gy or x
            xy2g[x] = xy2g[y] = g
            g2xy[g] |= {x, y}
        elif 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()
    ans = -N
    for xys in g2xy.values():
        nx, ny = 0, 0
        for xy in xys:
            if xy > 0:
                nx += 1
            else:
                ny += 1
        ans += nx * ny
    return ans

print(main())

Solution (Python):
def f_must_be_rectangular(N, Positions):
    
    
    import sys
    sys.setrecursionlimit(10**6)

    vertex_maxsize = 10**5 + 5
    target = [[] for _ in range(2 * vertex_maxsize)]
    is_visited = [False] * (2 * vertex_maxsize)
    for x, y in Positions:
        y += vertex_maxsize  
        target[x].append(y)
        target[y].append(x)

    def dfs(v):
        if is_visited[v]:
            return
        is_visited[v] = True
        count[v // vertex_maxsize] += 1  
        for u in target[v]:
            dfs(u)  

    ans = 0
    for i in range(2 * vertex_maxsize):
        if is_visited[i]:
            continue
        count = [0] * 2
        dfs(i)  
        
        
        ans += count[0] * count[1]
    return ans - N  

N = int(input())
Positions = [[int(i) for i in input().split()] for j in range(N)]
print(f_must_be_rectangular(N, Positions))

Solution (Python):
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 or gy == 0:
            g = gx or gy or x
            xy2g[x] = xy2g[y] = g
            g2xy[g] |= {x, y}
        elif 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()
    ans = -N
    for xys in g2xy.values():
        nx = 0
        for xy in xys:
            if xy > 0:
                nx += 1
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):
def f_must_be_rectangular(N, Positions):
    
    
    import sys
    sys.setrecursionlimit(10**7)

    vertex_maxsize = 10**5 + 5
    target = [[] for _ in range(2 * vertex_maxsize)]
    is_visited = [False] * (2 * vertex_maxsize)
    count = [0] * 2
    for x, y in Positions:
        y += vertex_maxsize  
        target[x].append(y)
        target[y].append(x)

    def dfs(v):
        if is_visited[v]:
            return
        is_visited[v] = True
        count[v // vertex_maxsize] += 1  
        for u in target[v]:
            dfs(u)  

    ans = 0
    for i in range(2 * vertex_maxsize):
        if is_visited[i]:
            continue
        count = [0] * 2
        dfs(i)  
        
        
        ans += count[0] * count[1]
    return ans - N  

N = int(input())
Positions = [[int(i) for i in input().split()] for j in range(N)]
print(f_must_be_rectangular(N, Positions))

Solution (Python):
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 or gy == 0:
            g = gx or gy or x
            xy2g[x] = xy2g[y] = g
            g2xy[g] |= {x, y}
        elif 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()
    ans = -N
    for xys in g2xy.values():
        nx = sum(xy > 0 for xy in xys)
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):
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 == gy != 0:
            continue
        elif gx != 0 and gy != 0:  
            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()
        elif gx != 0:
            xy2g[y] = gx
            g2xy[gx].add(y)
        elif gy != 0:
            xy2g[x] = gy
            g2xy[gy].add(x)
        else:
            g = x
            xy2g[x] = xy2g[y] = g
            g2xy[g] |= {x, y}
    ans = -N
    for xys in g2xy.values():
        nx = sum(xy > 0 for xy in xys)
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):
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 = [None] * (10 ** 5 * 2 + 1)
    g2xy = defaultdict(set)
    for x, y in xy:
        y = -y
        gx = xy2g[x]
        gy = xy2g[y]
        if gx == gy is not None:
            continue
        elif gx is not None is not 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(xy > 0 for xy in xys)
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):
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 == gy != 0:
            continue
        elif gx != 0 != 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(xy > 0 for xy in xys)
        ny = len(xys) - nx
        ans += nx * ny
    return ans

print(main())

Solution (Python):

from collections import defaultdict
from collections import deque
from heapq import heappush, heappop
import sys
import math
import bisect
import random
def LI(): return list(map(int, sys.stdin.readline().split()))
def I(): return int(sys.stdin.readline())
def LS():return list(map(list, sys.stdin.readline().split()))
def S(): return list(sys.stdin.readline())[:-1]
def IR(n):
    l = [None for i in range(n)]
    for i in range(n):l[i] = I()
    return l
def LIR(n):
    l = [None for i in range(n)]
    for i in range(n):l[i] = LI()
    return l
def SR(n):
    l = [None for i in range(n)]
    for i in range(n):l[i] = S()
    return l
def LSR(n):
    l = [None for i in range(n)]
    for i in range(n):l[i] = LS()
    return l
sys.setrecursionlimit(1000000)
mod = 1000000007


def A():
    s = S()
    for i in range(len(s)-1):
        if s[i] == s[i+1]:
            print("Bad")
            quit()
    print("Good")
    return


def B():
    n,l = LI()
    a = [i+l for i in range(n)]
    ans = sum(a)
    m = n+l-1
    for i in a[::-1]:
        if i < 0:break
        m = min(m,i)
    ans -= m
    print(ans)
    return


def gcd(a,b):
    if a == 0:
        return b
    return gcd(b%a,a)
def C():
    a,b,c,d = LI()
    g = gcd(c,d)
    l = c*d//g
    sc = b//c-(a-1)//c
    sd = b//d-(a-1)//d
    sl = b//l-(a-1)//l
    print(b-a+1-sc-sd+sl)
    return


def D():
    n = I()
    w = LIR(n)
    w.sort(key = lambda x: x[1])
    t = 0
    for a,b in w:
        if t+a > b:
            print("No")
            quit()
        t += a
    print("Yes")
    return


def E():
    n,k = LI()
    if k == 0:
        m = (n*(n-1))//2
        print(m)
        for i in range(1,n+1):
            for j in range(i+1,n+1):
                print(i,j)
    else:
        if k > ((n-1)*(n-2))//2:
            print(-1)
        else:
            ans = []
            for i in range(2,n+1):
                ans.append((1,i))
            m = ((n-1)*(n-2))//2
            for i in range(2,n+1):
                for j in range(i+1,n+1):
                    if m == k:break
                    ans.append((i,j))
                    m -= 1
                if m == k:break
            print(len(ans))
            for i,j in ans:
                print(i,j)
    return


def F():
    n = I()
    v = defaultdict(list)
    l = []
    f = defaultdict(lambda : 1)
    for i in range(n):
        x,y = LI()
        y += 100001
        v[x].append(y)
        v[y].append(x)
        if f[x]:
            f[x] = 0
            l.append(x)
        if f[y]:
            f[y] = 0
            l.append(y)
    ans = 0
    bfs = defaultdict(lambda : 1)
    q = deque()
    for i in l:
        if bfs[i]:
            bfs[i] = 0
            q.append(i)
            nx = (i < 100001)
            ny = 1-nx
            while q:
                x = q.popleft()
                for y in v[x]:
                    if bfs[y]:
                        bfs[y] = 0
                        q.append(y)
                        k = (y < 100001)
                        nx += k
                        ny += (1-k)
            ans += nx*ny
    print(ans-n)
    return


if __name__ == "__main__":
    F()