公式

H - 連結成分 / Connected Components 解説 by KoD


この問題はマージテク (weighted union rule) と呼ばれるテクニックを用いて解くことができます。

連結成分ごとに、その成分に属する頂点を配列などのデータ構造で管理することにします。連結成分 \(A, B\) をマージするとき、\(|A| \leq |B|\) なら \(A\) に属する頂点を \(B\) に対応するデータ構造に push し、\(|A| \gt |B|\) なら \(B\) に属する頂点を \(A\) に対応するデータ構造に push します。

各頂点について、何回別のデータ構造に移されるか考えてみましょう。\(|A| + |B| \geq 2 \min(|A|, |B|)\) から、ある頂点が別のデータ構造に移されるとき、その頂点が属する連結成分の大きさは \(2\) 倍以上になることがわかります。連結成分の大きさは最大でも \(N\) 以下なので、移された回数を \(k\) とおくと \(2^k \leq N\) すなわち \(k \leq \log_{2}{N}\) が成り立ちます。

全ての頂点について上記の値を合計した値は \(N \log_{2}{N}\) であるので、この問題を \(O(N \log N)\) で解くことができました。

実装例 (C++) :

#include <bits/stdc++.h>
using namespace std;

int main() {
    int n, q;
    cin >> n >> q;
    vector<int> par(n);
    vector<vector<int>> list(n);
    for (int i = 0; i < n; ++i) {
        par[i] = i;
        list[par[i]].push_back(i);
    }
    while (q--) {
        int t;
        cin >> t;
        if (t == 1) {
            int u, v;
            cin >> u >> v;
            u -= 1, v -= 1;
            if (par[u] != par[v]) {
                if (list[par[u]].size() < list[par[v]].size()) {
                    swap(u, v);
                }
                const int a = par[u], b = par[v];
                for (const int x : list[b]) {
                    list[a].push_back(x);
                    par[x] = a;
                }
                list[b].clear();
            }
        } else {
            int u;
            cin >> u;
            u -= 1;
            auto& vec = list[par[u]];
            sort(begin(vec), end(vec));
            const int m = vec.size();
            for (int i = 0; i < m; ++i) {
                cout << vec[i] + 1 << " \n"[i + 1 == m];
            }
        }
    }
}

投稿日時:
最終更新: