Problem p03108 (Python) — buggy submission, verdict: Time Limit Exceeded

class UnionFind:
    def __init__(self, n):
        self.v = [-1 for _ in range(n)]  # 根(負): 連結頂点数 * (-1) / 子(正): 根の頂点番号(0-indexed)

    def find(self, x):  # xを含む木における根の頂点番号を返す
        if self.v[x] < 0:  # (負)は根
            return x
        else:  # 根の頂点番号
            self.v[x] = self.find(self.v[x])  # uniteでは, 旧根に属する頂点の根が旧根のままなので更新
            return self.v[x]

    def unite(self, x, y):  # 違う根に属していたらrankが低くなるように連結
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if -self.v[x] < -self.v[y]:  # size比較, 　(-1) * (連結頂点数 * (-1)), (正)同士の大小比較
            x, y = y, x  # 連結頂点数が少ない方をyにすると, findでの更新回数が減る？
        self.v[x] += self.v[y]  # 連結頂点数の和を取る, 連結頂点数 * (-1)
        self.v[y] = x  # 連結頂点数が少ないy(引数yの根の頂点番号)の根をx(引数xの根の頂点番号)にする

    def root(self, x):
        return self.v[x] < 0  # (負)は根

    def same(self, x, y):
        return self.find(x) == self.find(y)  # 同じ根に属するか

    def size(self, x):
        return -self.v[self.find(x)]  # 連結頂点数を返す


N, M = map(int, input().split())
e = []
for _ in range(M):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    e.append((a, b))

init = N * (N - 1) // 2
uf = UnionFind(N)

ans = [-1 for _ in range(M)]
cc = 0
for i in sorted(range(M), reverse=True):
    ans[i] = init - cc
    a, b = e[i]
    sa, sb = uf.size(a), uf.size(b)
    if sa == 1:
        if sb == 1:
            cc += 1
        else:  # sb > 1
            cc -= sb * (sb - 1) // 2
            cc += sb * (sb + 1) // 2
    else:
        if sb == 1:
            cc -= sa * (sa - 1) // 2
            cc += sa * (sa + 1) // 2
        else:
            # sa > 1 and sb > 1
            if uf.same(a, b):
                pass
            else:
                cc -= sa * (sa - 1) // 2
                cc -= sb * (sb - 1) // 2
                cc += (sa + sb) * (sa + sb - 1) // 2
    uf.unite(a, b)

for i in range(M):
    print(ans[i])


Error: TLE

Fixed submission:

class UnionFind:
    def __init__(self, n):
        self.v = [-1 for _ in range(n)]  # 根(負): 連結頂点数 * (-1) / 子(正): 根の頂点番号(0-indexed)

    def find(self, x):  # xを含む木における根の頂点番号を返す
        if self.v[x] < 0:  # (負)は根
            return x
        else:  # 根の頂点番号
            self.v[x] = self.find(self.v[x])  # uniteでは, 旧根に属する頂点の根が旧根のままなので更新
            return self.v[x]

    def unite(self, x, y):  # 違う根に属していたらrankが低くなるように連結
        x = self.find(x)
        y = self.find(y)
        if x == y:
            return
        if -self.v[x] < -self.v[y]:  # size比較, 　(-1) * (連結頂点数 * (-1)), (正)同士の大小比較
            x, y = y, x  # 連結頂点数が少ない方をyにすると, findでの更新回数が減る？
        self.v[x] += self.v[y]  # 連結頂点数の和を取る, 連結頂点数 * (-1)
        self.v[y] = x  # 連結頂点数が少ないy(引数yの根の頂点番号)の根をx(引数xの根の頂点番号)にする

    def root(self, x):
        return self.v[x] < 0  # (負)は根

    def same(self, x, y):
        return self.find(x) == self.find(y)  # 同じ根に属するか

    def size(self, x):
        return -self.v[self.find(x)]  # 連結頂点数を返す


N, M = map(int, input().split())
e = []
uf = UnionFind(N)
for _ in range(M):
    a, b = map(int, input().split())
    a -= 1
    b -= 1
    e.append((a, b))

init = N * (N - 1) // 2
ans = [init for _ in range(M)]

for i in sorted(range(M), reverse=True):
    cc = 0
    for v in range(N):
        if uf.root(v):
            set_size = uf.size(v)
            cc += set_size * (set_size - 1) // 2
    ans[i] -= cc
    a, b = e[i]
    uf.unite(a, b)

for i in range(M):
    print(ans[i])