
配点: 450 点 / 実行時間制限: 2 sec / メモリ制限: 1024 MB / Difficulty: ???
問題概要
頂点
辺の無向グラフがある。頂点には
の番号がついており、
番目の辺は頂点
と頂点
を結んでいる。
について、次の問題を解け。
「頂点を一つ選び、選んだ頂点とそれに接続する全ての辺をグラフから削除する」という操作を繰り返すことで、次の条件を満たすことができるか判定し、不可能なら-1
を出力、可能なら条件を満たすために少なくとも何回操作を行う必要があるか求めよ。
- 頂点
から辺をたどって到達できる頂点の集合が、頂点
のちょうど
個からなる。
制約
- 入力はすべて整数
考察
この問題は「可能性の判定」と「操作回数の最小化」の2つのパートに分けて考えることができる。
可能性の判定
「頂点 から辺をたどって到達できる頂点の集合が、頂点
のちょうど
個からなる」状態というのは、
- ある
に対して、頂点
から任意の頂点
へ、 番号が
より大きな頂点を経由せずに到達できる
ということである。
番号が より大きな頂点を経由したくないのだから、このような頂点を全て削除した上で、
- 頂点
が含まれる連結成分の大きさが
(残りの連結成分がただ1つ)
であるならば、この については問題文の条件を満たすことができると言える。
これを後の実装のために言い換えておくと、
- 「辺で連結されている2頂点のうち、番号の大きい方が
以下であるような辺のみで構成される部分グラフ」上で、頂点
を含む連結成分の大きさが
である
ということが判定条件になる。
操作回数の最小化
さて、前項では番号が より大きな頂点を全て削除したが、実は頂点を消し過ぎていることに気づくだろうか?
サンプルケース1を例にとって考えてみる。
のときについて、番号が
より大きい頂点をすべて削除した。
これでも問題文の条件は満たしているのだが、よく考えると頂点 は残しておいてもいいような気がする。
復活!
頂点 は頂点
と元のグラフ上で隣接していないため、上図のように残しても頂点
から頂点
へは到達できず、結果的に条件は満たしていることになる。
つまり、削除すべきなのは「番号が 以下の頂点に隣接している頂点のみ」であることが分かった。
この個数は、「番号が 以下の頂点を端点の1つとして持つ辺のみで構成される部分グラフ」上で、頂点
を含む連結成分の大きさから、
を引いたものに等しい。画像も参考に...
実装にあたって
さて、あとは上述の考察をもとに実装していくわけだが、まずデータ構造としては Union-Find が適当である。これは、各パートで連結成分の大きさを求める必要があるためだ。
また、2つのパートでは考えるべき部分グラフが異なっているので、 Union-Find も2つ用意することで、部分グラフを構築しながら可能性の判定と操作の最小化を同時に行うことができるようになる。実装例では前者をuf_1
、後者をuf_2
とした。
なお、実装例では 0-indexed になっていることに注意してほしい。
実装例
#include <bits/stdc++.h> using namespace std; #if __has_include(<atcoder/all>) #include <atcoder/all> using namespace atcoder; #endif #define rep(i, start, end) for (auto i = (start); (i) < (end); (i)++) template <class T> using Graph = vector<vector<T>>; // ======================================== // int main() { int N, M; cin >> N >> M; Graph<int> G(N); rep(i, 0, M) { int u, v; cin >> u >> v; u--, v--; G[u].push_back(v); G[v].push_back(u); } dsu uf_1(N), uf_2(N); rep(k, 0, N) { for (auto &&v : G[k]) { if (v < k) uf_1.merge(k, v); else uf_2.merge(k, v); } if (uf_1.size(0) == k + 1) cout << uf_2.size(0) - (k + 1) << endl; else cout << -1 << endl; } return 0; }
実装時間: 45分
コメント
条件の言い換えを要求されている。ただ、個人的には D 問題よりも割とすんなり解けた気がする。