Buggy python submission (verdict: Runtime Error):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def creat_graph(n,line_lst,directed=False):
    graph = npzeros(n)
    for line in line_lst:
        graph[line[0]][line[1]] = 1
        if not directed:graph[line[1]][line[0]] = 1 # 無向グラフの場合
    return graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for a,b in AB:
        ans.append(count)
        if labels[a] != labels[b]:
            count -= labels[labels == labels[a]].size * labels[labels == labels[b]].size
    
        graph[a,b] = 1
        graph[b,a] = 1
        _,labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Time Limit Exceeded):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def creat_graph(n,line_lst,directed=False):
    graph = npzeros(n)
    for line in line_lst:
        graph[line[0]][line[1]] = 1
        if not directed:graph[line[1]][line[0]] = 1 # 無向グラフの場合
    return graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = init_array_2dim(0,N,N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for a,b in AB:
        ans.append(count)
        if labels[a] != labels[b]:
            count -= len(labels[labels == labels[a]]) * len(labels[labels == labels[b]])
    
        graph[a][b] = 1
        graph[a][b] = 1
        _, labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Time Limit Exceeded):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def creat_graph(n,line_lst,directed=False):
    graph = npzeros(n)
    for line in line_lst:
        graph[line[0]][line[1]] = 1
        if not directed:graph[line[1]][line[0]] = 1 # 無向グラフの場合
    return graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = init_array_2dim(0,N,N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for ab in AB:
        ans.append(count)
        if labels[ab[0]] != labels[ab[1]]:
            count -= len(labels[labels == labels[ab[0]]]) * len(labels[labels == labels[ab[1]]])
    
        graph[ab[0]][ab[1]] = 1
        graph[ab[1]][ab[0]] = 1
        _, labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Time Limit Exceeded):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def creat_graph(n,line_lst,directed=False):
    graph = npzeros(n)
    for line in line_lst:
        graph[line[0]][line[1]] = 1
        if not directed:graph[line[1]][line[0]] = 1 # 無向グラフの場合
    return graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = init_array_2dim(0,N,N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for ab in AB:
        ans.append(count)
        if labels[ab[0]] != labels[ab[1]]:
            count -= labels[labels == labels[ab[0]]].shape[0] * labels[labels == labels[ab[1]]].shape[0]
    
        graph[ab[0]][ab[1]] = 1
        graph[ab[1]][ab[0]] = 1
        _, labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Runtime Error):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def creat_graph(n,line_lst,directed=False):
    graph = npzeros(n)
    for line in line_lst:
        graph[line[0]][line[1]] = 1
        if not directed:graph[line[1]][line[0]] = 1 # 無向グラフの場合
    return graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for ab in AB:
        ans.append(count)
        if labels[ab[0]] != labels[ab[1]]:
            count -= labels[labels == labels[ab[0]]].shape[0] * labels[labels == labels[ab[1]]].shape[0]
    
        graph[ab[0],ab[1]] = 1
        graph[ab[1],ab[0]] = 1
        _, labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Runtime Error):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,line_lst,directed=False):
    visited,graph = init_array_1dim(False,n),init_array_2dim(False,n,n)
    for line in line_lst:
        graph[line[0]][line[1]] = True
        if not directed:graph[line[1]][line[0]] = True # 無向グラフの場合
    return visited,graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    np = import_module("numpy")
    _, labels = connected_components(graph)
    for ab in AB:
        ans.append(count)
        if labels[ab[0]] != labels[ab[1]]:
            count -= np.where(labels == labels[ab[0]])[0].shape[0]*np.where(labels == labels[ab[1]])[0].shape[0]
    
        graph[ab[0],ab[1]] = 1
        graph[ab[1],ab[0]] = 1
        _, labels = connected_components(graph)

    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Runtime Error):
import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,line_lst,directed=False):
    visited,graph = init_array_1dim(False,n),init_array_2dim(False,n,n)
    for line in line_lst:
        graph[line[0]][line[1]] = True
        if not directed:graph[line[1]][line[0]] = True # 無向グラフの場合
    return visited,graph

def solution():
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = init_array_1dim(0,M)
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    np = import_module("numpy")
    _, labels = connected_components(graph)
    for i in range(M):
        ans[M-i-1] = count
        if labels[AB[i][0]] != labels[AB[i][1]]:
            count -= np.where(labels == labels[AB[i][0]])[0].shape[0]*np.where(labels == labels[AB[i][1]])[0].shape[0]
    
        graph[AB[i][0],AB[i][1]] = 1
        graph[AB[i][1],AB[i][0]] = 1
        _, labels = connected_components(graph)

    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Runtime Error):
INF = float("inf")

import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph,directed=False:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,line_lst,directed=False):
    visited,graph = init_array_1dim(False,n),init_array_2dim(False,n,n)
    for line in line_lst:
        graph[line[0]][line[1]] = True
        if not directed:graph[line[1]][line[0]] = True # 無向グラフの場合
    return visited,graph

def solution():
    np = import_module('numpy')
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = init_array_1dim(0,M)
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph,True)
    for i in range(M):
        ans[M-i-1] = count
        graph[AB[i][0],AB[i][1]] = 1
        graph[AB[i][1],AB[i][0]] = 1
        
        if labels[AB[i][0]] != labels[AB[i][1]]:
            count -= np.where(labels == labels[AB[i][0]])[0].shape[0]*np.where(labels == labels[AB[i][1]])[0].shape[0]
        pred, labels = connected_components(graph,True)
    
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Runtime Error):
INF = float("inf")

import sys
sys.setrecursionlimit(10**9)
input = sys.stdin.readline
input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,input_func:[input_func() for _ in range(n)]

import importlib
import_module = lambda module_name:importlib.import_module(module_name)

init_array_1dim = lambda value,n:[value]*n # 1次元配列を生成
init_array_2dim = lambda value,n,m:[init_array_1dim(value,n) for _ in range(m)] # 2次元配列を生成

gcd_base = lambda value1,value2:import_module('fractions').gcd(value1,value2) # 最大公約数（2値)
gcd = lambda lst:import_module('functools').reduce(gcd_base,lst) # 最大公約数（リスト）
lcm_base = lambda value1,value2:(value1*value2)//gcd_base(value1,value2) # 最小公倍数(2値)
lcm = lambda lst:import_module('functools').reduce(lcm_base,lst,1) # 最小公倍数(リスト)

permutations = lambda lst,n:import_module('itertools').permutations(lst,n) # 順列
combinations = lambda lst,n:import_module('itertools').combinations(lst,n) # 組み合わせ
product = lambda lst1,lst2:import_module('itertools').product(lst1,lst2) # 二つのリストの直積

round = lambda value:round(value) # 四捨五入
ceil = lambda value:import_module('math').ceil(value) # 切り上げ
floor = lambda value:import_module('math').floor(value) # 切り捨て

# キュー
init_q = lambda lst: import_module('collections').deque(lst)
q_pop = lambda q: q.popleft() # 先頭から取り出し
q_push = lambda q,value:q.appendleft(value) # 先頭に追加（値）
q_pushlist = lambda q,lst:q.extendleft(lst) # 先頭に追加（リスト）
q_append = lambda q,value:q.append(value) # 末尾に追加（値）
q_appendlist = lambda q,lst:q.extend(lst) # 末尾に追加（リスト）

# プライオリティキュー
init_heap = lambda a:import_module('heapq').heapify(a)
heap_push = lambda a,v:import_module('heapq').heappush(a,v)
heap_pop = lambda a:import_module('heapq').heappop(a) # 最小値を取り出す

# 二分探索
bisect_left = lambda a,x:import_module('bisect').bisect_left(a,x)
bisect_right = lambda a,x:import_module('bisect').bisect_left(a,x)
insert_left = lambda a,x:import_module('bisect').insert_left(a,bisect_left(a,x))
insert_right = lambda a,x:import_module('bisect').insert_right(a,bisect_right(a,x))

# numpy
nparray = lambda lst:import_module('numpy').array(lst)
npzeros = lambda n:import_module('numpy').zeros((n,n))
npones = lambda n:import_module('numpy').ones((n,n))

# 累積和、累積積
cumsum = lambda a:import_module('numpy').cumsum(import_module('numpy').array(a)) # 累積和
cumprod = lambda a:import_module('numpy').cumprod(import_module('numpy').array(a)) # 累積積

def two_pointers(lst,threshold,ope='sum'):
    l,r = 0,0
    base = 0 
    if ope == 'sum':base = 0
    if ope == 'prd':base = 1
    diff = 0
    while r < len(lst):
        val = 0
        if ope == 'sum':val = base + lst[r]
        if ope == 'prd':val = base * lst[r]
        if val <= threshold:
            if ope == 'sum':base += lst[r]
            if ope == 'prd':base *= lst[r]
            diff = max(diff, r-l+1)
            r += 1
        elif l == r:
            r += 1
            l += 1
        else:
            if ope == 'sum':base -= lst[l]
            if ope == 'prd':base //= lst[l]
            l += 1
    return diff

# グラフ
to_csr_matrix = lambda graph:import_module('scipy.sparse').csr_matrix(graph)
connected_components = lambda graph:import_module('scipy.sparse.csgraph').connected_components(csgraph=to_csr_matrix(graph), directed=False, return_labels=True) # 有向無向グラフ
shortest_path = lambda graph:import_module('scipy.sparse.csgraph').shortest_path(to_csr_matrix(graph), directed=False) # 最短経路

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,line_lst,directed=False):
    visited,graph = init_array_1dim(False,n),init_array_2dim(False,n,n)
    for line in line_lst:
        graph[line[0]][line[1]] = True
        if not directed:graph[line[1]][line[0]] = True # 無向グラフの場合
    return visited,graph

def solution():
    np = import_module('numpy')
    # ここに実装
    N,M = input_ints()
    graph = npzeros(N)
    AB = [[line[0]-1,line[1]-1] for line in input_lines(M,input_ints_list)]
    AB.reverse()

    ans = init_array_1dim(0,M)
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    # 線を後ろから追加していく感じで処理を進める
    _, labels = connected_components(graph)
    for i in range(M):
        ans[M-i-1] = count
        graph[AB[i][0],AB[i][1]] = 1
        graph[AB[i][1],AB[i][0]] = 1
        
        if labels[AB[i][0]] != labels[AB[i][1]]:
            count -= np.where(labels == labels[AB[i][0]])[0].shape[0]*np.where(labels == labels[AB[i][1]])[0].shape[0]
        _, labels = connected_components(graph)
    
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Buggy python submission (verdict: Time Limit Exceeded):
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.insert(0,count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()

Fixed submission:
import sys

input_int = lambda:int(input())
input_ints = lambda:map(int,input().split())
input_ints_list = lambda:list(input_ints())
input_str = lambda:input()
input_strs = lambda:input().split()
input_lines = lambda n,f:[f() for _ in range(n)]

import functools,fractions
gcd = lambda a:functools.reduce(fractions.gcd,a) # 最大公約数（リスト）
lcm_base = lambda a,b:(a*b)//fractions.gcd(a,b) # 最小公倍数(2値)
lcm = lambda a:functools.reduce(lcm_base,a,1) # 最小公倍数(リスト)

import itertools
permutations = lambda a,n:itertools.permutations(a,n) # 順列
combinations = lambda a,n:itertools.combinations(a,n) # 組み合わせ
product = lambda a,b:itertools.product(a,b) # 二つのリストの直積

init_array_1dim = lambda v,n:[v for _ in range(n)]
init_array_2dim = lambda v,n,m:[[v for _ in range(n)] for _ in range(m)]

import math
# 四捨五入はround
ceil = lambda a:math.ceil(a) # 切り上げ
floor = lambda a:math.floor(a) # 切り捨て

class UnionFind(object):
    def __init__(self, n):
        self.par = [i for i in range(n)]
        self.rank = [0 for _ in range(n)]
        self.size = [1 for _ in range(n)]

    def find(self, x):
        if self.par[x] == x:
            return x
        else:
            self.par[x] = self.find(self.par[x])
            return self.par[x]

    def union(self, x, y):
        x = self.find(x)
        y = self.find(y)
        if x != y:
            if self.rank[x] < self.rank[y]:
                x, y = y, x
            if self.rank[x] == self.rank[y]:
                self.rank[x] += 1
            self.par[y] = x
            self.size[x] += self.size[y]

    def is_same(self, x, y):
        return self.find(x) == self.find(y)

    def get_size(self, x):
        x = self.find(x)
        return self.size[x]

# グラフで全部到達できる経路が何個あるか
def dfs_graph(v,n,graph,visited):
	if all(visited):return 1 # 全部到達できたら1を返す
	ans = 0
	for i in range(n):
		if not graph[v][i]:continue # 結ぶ線がないときはスキップ
		if visited[i]:continue # 到達済みだったらスキップ
		visited[i] = True
		ans += dfs_graph(i,n,graph,visited)
		visited[i] = False
	return ans

# ワーシャルフロイド（グラフの要素数、グラフ）
def warshall_floyd(n,graph):
	d = init_array_2dim(float("inf"),n,n)
	# 初期化
	for i in range(n):
		d[i][i] = 0
		for j in range(n):
			if graph[i][j]:d[i][j] = 1
	# 距離算出
	for k in range(n):
		for i in range(n):
			for j in range(n):
				d[i][j] = min(d[i][j],d[i][k] + d[k][j])
	return d

# グラフを初期化（グラフの要素数、グラフの線の両端、有向無向）
def init_graph(n,a,directed=False):
    visited = init_array_1dim(False,n)
    graph = init_array_2dim(False,n,n)
    for e in a:
        graph[e[0]-1][e[1]-1] = True
        if not directed:graph[e[1]-1][e[0]-1] = True # 無向グラフの場合
    return visited,graph

def solution():
	# ここに実装
    N,M = input_ints()
    AB = input_lines(M,input_ints_list)

    ans = []
    count = N*(N-1)//2 # 線一つもない時は2つ選んだ組み合わせ数
    uf = UnionFind(N)
    # 線を後ろから追加していく感じで処理を進める
    while(AB):
        ans.append(count)
        a,b = AB.pop()
        if not uf.is_same(a-1,b-1):
            count -= uf.get_size(a-1)*uf.get_size(b-1)
            uf.union(a-1,b-1)
    
    ans.reverse()
    for _ans in ans:
        print(_ans)
	
if __name__ == '__main__':
    solution()