Buggy python submission (verdict: Time Limit Exceeded):
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




if __name__ == '__main__':
    N = int(input())
    points = [tuple(map(int,input().split())) for _ in range(N)]
    print(solve(points))

Fixed submission:
from bisect import bisect_left
N = int(input())
points = list(tuple(map(int,input().split())) for _ in range(N))

X = sorted(set(x for x,y in points))
Y = sorted(set(y for x,y in points))

points = set((bisect_left(X, x), bisect_left(Y, y)) for x,y in points)

xty = [list() for _ in range(len(X))]
ytx = [list() for _ in range(len(Y))]
for x,y in points:
    xty[x].append(y)
    ytx[y].append(x)

area = 0
visited = set()

for p in points:
    if p in visited:
        continue

    stack = [p]

    dx = set()
    dy = set()

    while stack:
        x,y = stack.pop()
        visited.add((x,y))
        if x not in dx:
            for yy in xty[x]:
                if yy != y:
                    stack.append((x,yy))
            dx.add(x)
        if y not in dy:
            for xx in ytx[y]:
                if xx != x:
                    stack.append((xx,y))
            dy.add(y)
    area += len(dx)*len(dy)

print(area-len(points))

Buggy python submission (verdict: Time Limit Exceeded):
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()
            dx.add(x)
            for y in xty[x]:
                if y not in dy:
                    stack_y.append(y)

        while stack_y:
            y = stack_y.pop()
            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))

Fixed submission:
from bisect import bisect_left
N = int(input())
points = list(tuple(map(int,input().split())) for _ in range(N))

X = sorted(set(x for x,y in points))
Y = sorted(set(y for x,y in points))

points = set((bisect_left(X, x), bisect_left(Y, y)) for x,y in points)

xty = [list() for _ in range(len(X))]
ytx = [list() for _ in range(len(Y))]
for x,y in points:
    xty[x].append(y)
    ytx[y].append(x)

area = 0
visited = set()

for p in points:
    if p in visited:
        continue

    stack = [p]

    dx = set()
    dy = set()

    while stack:
        x,y = stack.pop()
        visited.add((x,y))
        if x not in dx:
            for yy in xty[x]:
                if yy != y:
                    stack.append((x,yy))
            dx.add(x)
        if y not in dy:
            for xx in ytx[y]:
                if xx != x:
                    stack.append((xx,y))
            dy.add(y)
    area += len(dx)*len(dy)

print(area-len(points))