TOP > データ構造
素集合データ構造(Union-Find)
説明
集合を高速に扱うためのデータ構造。集合を合併する操作(unite), ある要素がどの集合に属しているか(find)を問い合わせる操作を行うことが出来る。
計算量
- $O(\alpha(n))$
$\alpha$ はアッカーマンの逆関数。
実装例
- $\mathrm{unite}(x, y)$: 集合 $x$ と $y$ を併合する. 併合済のとき $\mathrm{false}$, 未併合のとき $\mathrm{true}$ が返される
- $\mathrm{find(k)}$: 要素 $k$ が属する集合を求める
- $\mathrm{size(k)}$: 要素 $k$ が属する集合の要素の数を求める
struct UnionFind {
vector< int > data;
UnionFind(int sz) {
data.assign(sz, -1);
}
bool unite(int x, int y) {
x = find(x), y = find(y);
if(x == y) return (false);
if(data[x] > data[y]) swap(x, y);
data[x] += data[y];
data[y] = x;
return (true);
}
int find(int k) {
if(data[k] < 0) return (k);
return (data[k] = find(data[k]));
}
int size(int k) {
return (-data[find(k)]);
}
};
検証
#define PROBLEM "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DSL_1_A"
#include "../../template/template.cpp"
#include "../union-find.cpp"
int main() {
int N, Q;
scanf("%d %d", &N, &Q);
UnionFind uf(N);
while(Q--) {
int t, x, y;
scanf("%d %d %d", &t, &x, &y);
if(t == 0) uf.unite(x, y);
else printf("%d\n", uf.find(x) == uf.find(y));
}
}
応用1: 2部グラフの頂点彩色
Union-Find を用いると $2$ 部グラフ判定とその副作用として彩色が可能。頂点を倍長して偶奇に分ける。隣接頂点を同じ色にするときは, $\mathrm{unite}(u, v)$ と $\mathrm{unite}(u+N, v+N)$, 異なる色にするときは $\mathrm{unite}(u+N, v)$ と $\mathrm{unite}(u, v+N)$ をする。
- bipartite_coloring(): 2部グラフの頂点彩色をする。2部グラフであるとき true, そうではないとき false を返す。
- [$k$]: 頂点 $k$ の色を返す。
struct BipartiteGraph : UnionFind
{
vector< int > color;
BipartiteGraph(int v) : color(v + v, -1), UnionFind(v + v) {}
bool bipartite_graph_coloring()
{
for(int i = 0; i < color.size() / 2; i++) {
int a = find(i);
int b = find(i + (int) color.size() / 2);
if(a == b) return (false);
if(color[a] < 0) color[a] = 0, color[b] = 1;
}
return (true);
}
bool operator[](int k)
{
return (bool(color[find(k)]));
}
};
応用2: 部分永続Union-Find
$t$ 番目のクエリを処理した時点における頂点 $x$ が含まれる連結成分の根や大きさを求めるクエリを $O(log N)$ で行うことができる。
struct PartiallyPersistentUnionFind {
vector< int > data;
vector< int > last;
vector< vector< pair< int, int > > > add;
PartiallyPersistentUnionFind() {}
PartiallyPersistentUnionFind(int sz) : data(sz, -1), last(sz, 1e9), add(sz) {
for(auto &vs : add) vs.emplace_back(-1, -1);
}
bool unite(int t, int x, int y) {
x = find(t, x);
y = find(t, y);
if(x == y) return false;
if(data[x] > data[y]) swap(x, y);
data[x] += data[y];
add[x].emplace_back(t, data[x]);
data[y] = x;
last[y] = t;
return true;
}
int find(int t, int x) {
if(t < last[x]) return x;
return find(t, data[x]);
}
int size(int t, int x) {
x = find(t, x);
return -prev(lower_bound(begin(add[x]), end(add[x]), make_pair(t, 0)))->second;
}
};
応用2: 完全永続Union-Find
永続配列を使うことで, Union-Find を完全永続させられる。メモリを結構消費するので注意。
依存ライブラリ Persistent-Array
struct PersistentUnionFind
{
PersistentArray< int, 3 > data;
PersistentUnionFind() {}
PersistentUnionFind(int sz)
{
data.build(vector< int >(sz, -1));
}
int find(int k)
{
int p = data.get(k);
return p >= 0 ? find(p) : k;
}
int size(int k)
{
return (-data.get(find(k)));
}
PersistentUnionFind unite(int x, int y)
{
x = find(x);
y = find(y);
if(x == y) return *this;
auto u = data.get(x);
auto v = data.get(y);
if(u < v) {
auto a = data.mutable_get(x);
*a += v;
auto b = data.mutable_get(y);
*b = x;
} else {
auto a = data.mutable_get(y);
*a += u;
auto b = data.mutable_get(x);
*b = y;
}
return *this;
}
};
応用3: Undo可能Union-Find
struct UnionFindUndo {
vector< int > data;
stack< pair< int, int > > history;
UnionFindUndo(int sz) {
data.assign(sz, -1);
}
bool unite(int x, int y) {
x = find(x), y = find(y);
history.emplace(x, data[x]);
history.emplace(y, data[y]);
if(x == y) return (false);
if(data[x] > data[y]) swap(x, y);
data[x] += data[y];
data[y] = x;
return (true);
}
int find(int k) {
if(data[k] < 0) return (k);
return (find(data[k]));
}
int size(int k) {
return (-data[find(k)]);
}
void undo() {
data[history.top().first] = history.top().second;
history.pop();
data[history.top().first] = history.top().second;
history.pop();
}
void snapshot() {
while(history.size()) history.pop();
}
void rollback() {
while(history.size()) undo();
}
};
応用4: 重み付きUnion-Find
template< typename T >
struct WeightedUnionFind {
vector< int > data;
vector< T > ws;
WeightedUnionFind() {}
WeightedUnionFind(int sz) : data(sz, -1), ws(sz) {}
int find(int k) {
if(data[k] < 0) return k;
auto par = find(data[k]);
ws[k] += ws[data[k]];
return data[k] = par;
}
T weight(int t) {
find(t);
return ws[t];
}
bool unite(int x, int y, T w) {
w += weight(x);
w -= weight(y);
x = find(x), y = find(y);
if(x == y) return false;
if(data[x] > data[y]) {
swap(x, y);
w *= -1;
}
data[x] += data[y];
data[y] = x;
ws[y] = w;
return true;
}
T diff(int x, int y) {
return weight(y) - weight(x);
}
};
#define PROBLEM "http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=DSL_1_B"
#include "../../template/template.cpp"
#include "../weighted-union-find.cpp"
int main() {
int N, M;
cin >> N >> M;
WeightedUnionFind< int > tree(N);
while(M--) {
int A, B, C, D;
cin >> A >> B >> C;
if(A == 0) {
cin >> D;
tree.unite(B, C, D);
} else {
if(tree.find(B) == tree.find(C)) {
cout << tree.diff(B, C) << endl;
} else {
cout << "?" << endl;
}
}
}
}