Problem p03108 (C++)

Buggy submission (status: Runtime Error):
#include <bits/stdc++.h>
using namespace std;
#define MD 1000000007
typedef long long int ll;
typedef pair<ll, ll> P;
template <typename T> std::string tostr(const T &t) {
  std::ostringstream os;
  os << t;
  return os.str();
}
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};

// 親の場合parには，-(集合のサイズ)を格納
vector<ll> par(1e6, -1), dep(1e6, 0);

// 木の根を求める
ll find(ll x) {
  if (par[x] < 0) {
    return x;
  } else {
    // return find(par[x])でも可能だが
    // 根をつなぎなおして計算量を落としている
    return par[x] = find(par[x]);
  }
}

// xとyが同じ集合に属するか否か
bool same(ll x, ll y) { return find(x) == find(y); }

// xとyの属する集合を併合
void unite(ll x, ll y) {
  // 同じ集合なら意味なし
  if (same(x, y))
    return;
  // rankの大小でどちらにくっつけるかを判定
  if (dep[x] < dep[y]) {
    par[y] += par[x];
    par[x] = y;
  } else {
    par[x] += par[y];
    par[y] = x;
    // 同じdepのときは，yをxにくっつけ
    // xのdepをプラスする
    if (dep[x] == dep[y])
      dep[x]++;
  }
}

// xの属する集合のサイズを求める
ll size(ll x) { return -par[find(x)]; }

int main() {
  ll n, m;
  cin >> n >> m;
  vector<P> pa(m + 2);
  vector<ll> ans(m + 2);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    pa[i] = {a, b};
  }
  // m番目の辺から順につなげていく
  // UnionFind利用
  for (int i = m - 1; i >= 0; i--) {
    // 全ての橋が壊れている場合
    if (i == m - 1) {
      ans[i] = n * (n - 1) / 2;
    } else {
      if (ans[i + 1] == 0 || same(pa[i + 1].first, pa[i + 1].second)) {
        ans[i] = ans[i + 1];
      } else {
        ll tmp = size(pa[i + 1].first) * size(pa[i + 1].second);
        ans[i] = ans[i + 1] - tmp;
        unite(pa[i + 1].first, pa[i + 1].second);
      }
    }
  }
  // 答え出力
  for (int i = 0; i < m; i++) {
    cout << ans[i] << endl;
  }
  return 0;
}

Fixed submission:
#include <bits/stdc++.h>
using namespace std;
#define MD 1000000007
typedef long long int ll;
typedef pair<ll, ll> P;
template <typename T> std::string tostr(const T &t) {
  std::ostringstream os;
  os << t;
  return os.str();
}
int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};

// 親の場合parには，-(集合のサイズ)を格納
vector<ll> par(1e6, -1), dep(1e6, 0);

// 木の根を求める
ll find(ll x) {
  if (par[x] < 0) {
    return x;
  } else {
    // return find(par[x])でも可能だが
    // 根をつなぎなおして計算量を落としている
    return par[x] = find(par[x]);
  }
}

// xとyが同じ集合に属するか否か
bool same(ll x, ll y) { return find(x) == find(y); }

// xとyの属する集合を併合
void unite(ll x, ll y) {
  // 同じ集合なら意味なし
  x = find(x);
  y = find(y);
  if (x == y)
    return;
  // rankの大小でどちらにくっつけるかを判定
  if (dep[x] < dep[y]) {
    par[y] += par[x];
    par[x] = y;
  } else {
    par[x] += par[y];
    par[y] = x;
    // 同じdepのときは，yをxにくっつけ
    // xのdepをプラスする
    if (dep[x] == dep[y])
      dep[x]++;
  }
}

// xの属する集合のサイズを求める
ll size(ll x) { return -par[find(x)]; }

int main() {
  ll n, m;
  cin >> n >> m;
  vector<P> pa(m + 2);
  vector<ll> ans(m + 2);
  for (int i = 0; i < m; i++) {
    int a, b;
    cin >> a >> b;
    a--;
    b--;
    pa[i] = {a, b};
  }
  // m番目の辺から順につなげていく
  // UnionFind利用
  for (int i = m - 1; i >= 0; i--) {
    // 全ての橋が壊れている場合
    if (i == m - 1) {
      ans[i] = n * (n - 1) / 2;
    } else {
      if (ans[i + 1] == 0 || same(pa[i + 1].first, pa[i + 1].second)) {
        ans[i] = ans[i + 1];
      } else {
        ll tmp = size(pa[i + 1].first) * size(pa[i + 1].second);
        ans[i] = ans[i + 1] - tmp;
        unite(pa[i + 1].first, pa[i + 1].second);
      }
    }
  }
  // 答え出力
  for (int i = 0; i < m; i++) {
    cout << ans[i] << endl;
  }
  return 0;
}