algorithm-dev

This documentation is automatically generated by online-judge-tools/verification-helper

View the Project on GitHub today2098/algorithm-dev

:heavy_check_mark: Union-Find(素集合データ構造)
(algorithm/DataStructure/UnionFind/union_find.hpp)

概要

「素集合データ構造 (disjoint-set data structure)」とは,互いに素な動的集合の族を管理する. 簡単にいうと,要素のグループ分けを管理するデータ構造である.

素集合データ構造に対する次の操作のアルゴリズムを「Union-Find」という. 1964年,Bernard A. Galler と Michael J. Fischer が効率的な手法を考案した.

本実装では「素集合森 (disjoint-set forest)」を作成しており,「union by size」と「経路圧縮」による工夫を行っている.

各クエリ処理に要する計算量は,アッカーマン関数の逆関数を $\operatorname{\alpha}(x)$ とすると,$\mathcal{O}(\operatorname{\alpha}(N))$ となる. アッカーマン関数の逆関数は,増加が非常に遅く,$x \leq 10^{80}$ に対し $\operatorname{\alpha}(x) \leq 4$ が成り立つため,実用上 $\mathcal{O}(1)$ とみなすことができる.

参考

  1. Bernard A. Galler and Michael J. Fisher. 1964. An improved equivalence algorithm. Commun. ACM 7, 5 (May 1964), 301–303. https://doi.org/10.1145/364099.364331.
  2. 大槻兼資著. “第11章 データ構造 (4):Union-Find”. 問題解決力を鍛える! アルゴリズムとデータ構造. 秋葉拓哉監修. 講談社, 2020, pp.181-190.
  3. “19 互いに素な集合族のためのデータ構造”. アルゴリズムイントロダクション 第4版 総合版. 近代科学社, 2024, pp.439-460.
  4. “素集合データ構造”. Wikipedia. https://ja.wikipedia.org/wiki/素集合データ構造.
  5. rsk0315. “α(n) とお近づきになりたい人のための記事”. HatenaBlog. https://rsk0315.hatenablog.com/entry/2020/11/14/170423.
  6. “巨大数:アッカーマン関数とは”. 高校数学の美しい物語. https://manabitimes.jp/math/1010.

問題

Verified with

Code

#ifndef ALGORITHM_UNION_FIND_HPP
#define ALGORITHM_UNION_FIND_HPP 1

#include <algorithm>
#include <cassert>
#include <utility>
#include <vector>

namespace algorithm {

class UnionFind {
    int m_vn;  // m_vn:=(要素数).
    int m_gn;  // m_gn:=(集合の数).
    // m_par[x]:=(要素xの親). 0未満の場合,xは代表元であり,値の絶対値は属する集合のサイズを表す.
    std::vector<int> m_par;

    int root_internal(int x) {
        if(m_par[x] < 0) return x;
        return m_par[x] = root_internal(m_par[x]);  // 経路圧縮.
    }

public:
    UnionFind() : UnionFind(0) {}
    explicit UnionFind(int n) : m_vn(n), m_gn(n), m_par(n, -1) {
        assert(n >= 0);
    }

    // 要素数を取得する.
    int vn() const { return m_vn; };
    // 集合の数を取得する.
    int gn() const { return m_gn; };
    // 要素xが属する集合の代表元を取得する.
    int root(int x) {
        assert(0 <= x and x < vn());
        return root_internal(x);
    }
    // 要素xが属する集合のサイズを取得する.
    int size(int x) {
        assert(0 <= x and x < vn());
        return -m_par[root_internal(x)];
    }
    // 要素x, yが同じ集合に属するか判定する.
    bool is_same(int x, int y) {
        assert(0 <= x and x < vn());
        assert(0 <= y and y < vn());
        return root_internal(x) == root_internal(y);
    }
    // 要素x, yが属するそれぞれの集合を合併する.
    bool unite(int x, int y) {
        assert(0 <= x and x < vn());
        assert(0 <= y and y < vn());
        x = root_internal(x), y = root_internal(y);
        if(x == y) return false;                    // Do nothing.
        if(-m_par[x] < -m_par[y]) std::swap(x, y);  // Merge technique (union by size).
        m_par[x] += m_par[y];
        m_par[y] = x;
        --m_gn;
        return true;
    }
    void reset() {
        m_gn = m_vn;
        std::fill(m_par.begin(), m_par.end(), -1);
    }
};

}  // namespace algorithm

#endif
#line 1 "algorithm/DataStructure/UnionFind/union_find.hpp"



#include <algorithm>
#include <cassert>
#include <utility>
#include <vector>

namespace algorithm {

class UnionFind {
    int m_vn;  // m_vn:=(要素数).
    int m_gn;  // m_gn:=(集合の数).
    // m_par[x]:=(要素xの親). 0未満の場合,xは代表元であり,値の絶対値は属する集合のサイズを表す.
    std::vector<int> m_par;

    int root_internal(int x) {
        if(m_par[x] < 0) return x;
        return m_par[x] = root_internal(m_par[x]);  // 経路圧縮.
    }

public:
    UnionFind() : UnionFind(0) {}
    explicit UnionFind(int n) : m_vn(n), m_gn(n), m_par(n, -1) {
        assert(n >= 0);
    }

    // 要素数を取得する.
    int vn() const { return m_vn; };
    // 集合の数を取得する.
    int gn() const { return m_gn; };
    // 要素xが属する集合の代表元を取得する.
    int root(int x) {
        assert(0 <= x and x < vn());
        return root_internal(x);
    }
    // 要素xが属する集合のサイズを取得する.
    int size(int x) {
        assert(0 <= x and x < vn());
        return -m_par[root_internal(x)];
    }
    // 要素x, yが同じ集合に属するか判定する.
    bool is_same(int x, int y) {
        assert(0 <= x and x < vn());
        assert(0 <= y and y < vn());
        return root_internal(x) == root_internal(y);
    }
    // 要素x, yが属するそれぞれの集合を合併する.
    bool unite(int x, int y) {
        assert(0 <= x and x < vn());
        assert(0 <= y and y < vn());
        x = root_internal(x), y = root_internal(y);
        if(x == y) return false;                    // Do nothing.
        if(-m_par[x] < -m_par[y]) std::swap(x, y);  // Merge technique (union by size).
        m_par[x] += m_par[y];
        m_par[y] = x;
        --m_gn;
        return true;
    }
    void reset() {
        m_gn = m_vn;
        std::fill(m_par.begin(), m_par.end(), -1);
    }
};

}  // namespace algorithm
Back to top page