Buggy python submission (verdict: Time Limit Exceeded):
# ABC120D - Decayed Bridges
class UnionFind:
    __slots__ = ["size", "first_idx", "parent", "rank"]

    def __init__(self, size: int, first_idx: int = 0) -> None:
        self.size = size
        self.first_idx = first_idx
        self.parent = list(range(size + first_idx))
        self.rank = [0] * (size + first_idx)

    def find(self, x: int) -> int:
        """Find the group (root) of vertex x"""
        if self.parent[x] == x:
            return x
        self.parent[x] = self.find(self.parent[x])
        return self.parent[x]

    def is_same(self, x: int, y: int) -> bool:
        """Return whether two vertices x and y are in the same group or not."""
        return self.find(x) == self.find(y)

    def unite(self, x: int, y: int) -> None:
        """Unite two groups of vertices x and y."""
        x, y = self.find(x), self.find(y)
        if x == y:
            return
        # unite a smaller one to a bigger one in order to balance trees
        if self.rank[x] < self.rank[y]:
            self.parent[x] = y
        else:
            self.parent[y] = x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1

    @property
    def groups(self) -> int:
        """Return the number of groups."""
        return len({self.find(x) for x in range(self.first_idx, self.size + self.first_idx)})
        # the below is a bit faster because it doesn't reconnect
        # return sum(i == j for i, j in enumerate(self.parent[self.first_idx:], self.first_idx))


def count_unreachable_islands(size: int, tree: UnionFind) -> int:
    reachable_count = sum(tree.is_same(i, j) for i in range(1, size + 1) for j in range(i + 1, size + 1))
    return size * (size - 1) // 2 - reachable_count


def main():
    N, M, *AB = map(int, open(0).read().split())
    uf = UnionFind(N, 1)
    ans = []
    for a, b in zip(*[iter(AB[::-1])] * 2):
        ans.append(count_unreachable_islands(N, uf))
        uf.unite(a, b)
    print("\n".join(map(str, ans[::-1])))


if __name__ == "__main__":
    main()


Fixed submission:
# ABC120D - Decayed Bridges
class UnionFind:
    __slots__ = ["size", "first_idx", "parents"]

    def __init__(self, size: int, first_index: int = 0) -> None:
        self.size = size
        self.first_idx = first_index
        self.parents = [-1] * (size + first_index)

    def find(self, x: int) -> int:
        """Find the group (root) of vertex x"""
        if self.parents[x] < 0:
            return x
        self.parents[x] = self.find(self.parents[x])
        return self.parents[x]

    def is_same(self, x: int, y: int) -> bool:
        """Return whether two vertices x and y are in the same group or not."""
        return self.find(x) == self.find(y)

    def unite(self, x: int, y: int) -> None:
        """Unite two groups of vertices x and y."""
        x, y = self.find(x), 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 get_size(self, x: int) -> int:
        """Get the size of the group vertex x belongs"""
        return self.parents[self.find(x)]

    @property
    def groups(self) -> int:
        """Return the number of groups."""
        return len({self.find(x) for x in range(self.first_idx, self.size + self.first_idx)})


def main():
    N, M, *AB = map(int, open(0).read().split())
    uf = UnionFind(N, 1)
    current_unreachable = N * (N - 1) // 2
    ans = []
    for a, b in zip(*[iter(AB[::-1])] * 2):
        ans.append(current_unreachable)
        if not uf.is_same(a, b):
            current_unreachable -= uf.get_size(a) * uf.get_size(b)
        uf.unite(a, b)
    print("\n".join(map(str, ans[::-1])))


if __name__ == "__main__":
    main()