C - ネットワークの整理 / Organizing the Network Editorial by admin
Claude 4.6 Opus (Thinking)概要
\(N\) 台のコンピュータと \(N\) 本のケーブルからなるネットワークから、\(K\) 本以下のケーブルを取り外して森(閉路のないグラフ)にできるかを判定する問題です。
考察
森の性質
森(閉路を含まないグラフ)には重要な性質があります。\(N\) 頂点で連結成分が \(C\) 個の森は、ちょうど \(N - C\) 本の辺を持ちます。つまり、辺が多すぎると必ず閉路ができるということです。
最小除去本数の特定
問題は「閉路をすべて消すために最低何本のケーブルを除去する必要があるか」に帰着されます。
辺を1本ずつ見ていったとき、以下の辺は閉路を作ります: 1. 自己ループ(\(A_i = B_i\)):1本で閉路をなす 2. 既に連結な2頂点を結ぶ辺:その2頂点間に既にパスがあるので、辺を追加すると閉路ができる
逆に、まだ連結でない2頂点を結ぶ辺は「木の辺」として安全に残せます。これらの木の辺を集めたものが全域森(spanning forest)となり、閉路を含みません。
結論
閉路を作る辺の本数を need_remove とすると、need_remove ≤ K なら Yes、そうでなければ No です。
アルゴリズム
Union-Find(素集合データ構造) を使って、各辺が閉路を作るかどうかを効率的に判定します。
- \(N\) 頂点の Union-Find を初期化する(各頂点が独立な成分)
- 各ケーブル \((A_i, B_i)\) について:
- \(A_i = B_i\)(自己ループ)の場合 → 閉路を作るので
need_remove++ - \(A_i\) と \(B_i\) が既に同じ連結成分にある場合(
uniteが失敗)→ 閉路を作るのでneed_remove++ - それ以外 → 2つの成分を統合(木の辺として採用)
- \(A_i = B_i\)(自己ループ)の場合 → 閉路を作るので
need_remove ≤ KならYes、そうでなければNo
具体例
\(N = 4, K = 1\) で辺が \((1,2), (2,3), (3,1), (3,4)\) の場合:
- \((1,2)\):1と2を統合 → 木の辺 ✓
- \((2,3)\):2と3を統合 → 木の辺 ✓
- \((3,1)\):1と3は既に同じ成分 → 除去必要(need_remove = 1)
- \((3,4)\):3と4を統合 → 木の辺 ✓
need_remove = 1 ≤ K = 1 なので Yes。
計算量
- 時間計算量: \(O(N \cdot \alpha(N))\)(\(\alpha\) はアッカーマン関数の逆関数で、実質 \(O(N)\))
- 空間計算量: \(O(N)\)
実装のポイント
自己ループ(\(A_i = B_i\))は
uniteを呼ぶ前に個別にチェックする。uniteは同じ頂点に対してfindを2回呼んで同じ根を返すので失敗扱いになるが、明示的にチェックするとコードの意図が明確になる。Union-Find には経路圧縮とランクによる統合を実装し、高速化する。
入力の頂点番号は 1-indexed なので、0-indexed に変換して処理する。
ソースコード
#include <bits/stdc++.h>
using namespace std;
struct UnionFind {
vector<int> parent, rank_;
UnionFind(int n) : parent(n), rank_(n, 0) {
iota(parent.begin(), parent.end(), 0);
}
int find(int x) {
while (parent[x] != x) {
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
bool unite(int x, int y) {
x = find(x); y = find(y);
if (x == y) return false;
if (rank_[x] < rank_[y]) swap(x, y);
parent[y] = x;
if (rank_[x] == rank_[y]) rank_[x]++;
return true;
}
};
int main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N, K;
cin >> N >> K;
UnionFind uf(N);
int need_remove = 0;
for(int i = 0; i < N; i++){
int a, b;
cin >> a >> b;
a--; b--;
if(a == b || !uf.unite(a, b)){
need_remove++;
}
}
cout << (need_remove <= K ? "Yes" : "No") << endl;
return 0;
}
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: