Official

C - Make it Forest Editorial by Nyaan


最適に辺を削除した時に、削除せずに残すことが可能な辺の本数に注目しましょう。
与えられるグラフが \(K\) 個の連結成分 \(C_1, C_2, \dots, C_K\) に分かれているとします。この時、各 \(C_i\) ごとに \(|C_i| - 1\) 本の辺を残すことができて、これが最大です。よって、残すことができる辺の本数の最大は、\(\sum_{i=1}^K C_i = N\) であることも合わせると、

\[ \begin{aligned} &\sum_{i=1}^K \left(\vert C_i \vert - 1\right) \\ &= \sum_{i=1}^K \vert C_i \vert - \sum_{i=1}^K 1 \\ &= N - K \end{aligned} \]

となります。これが残すことができる辺の本数の最大なので、元のグラフを森にするために削除すべき辺の本数の最小値は

\[M - (N - K)\]

となります。よって、連結成分の個数 \(K\) が求まればこの問題を解くことができるとわかりました。

連結成分の個数を数える問題は ABC284 の C 問題 ですでに取り上げました。上記の問題の解説を読めばわかる通り、連結成分の個数は DFS や BFS などのグラフ探索アルゴリズムか素集合データ構造(Union-Find) を用いれば高速に個数を求めることができます。

以上の考察によりこの問題を解くことができました。計算量は \(\mathrm{O}(N + M)\)\(\mathrm{O}((N+M) \alpha(N))\) (\(\alpha(N)\) はアッカーマン関数の逆関数) 程度で十分高速です。

  • 実装例(C++)
#include <iostream>
using namespace std;
#include "atcoder/dsu.hpp"

int main() {
  int N, M;
  cin >> N >> M;
  atcoder::dsu uf(N);
  int K = N;
  for (int i = 0; i < M; i++) {
    int u, v;
    cin >> u >> v;
    --u, --v;
    if (!uf.same(u, v)) uf.merge(u, v), K--;
  }
  cout << M - (N - K) << "\n";
}

posted:
last update: