cp-library

競技プログラミング用 C++ ライブラリ

View the Project on GitHub qandori/cp-library

:heavy_check_mark: Block Cut Tree

graph/block_cut_tree.hpp · GitHub で見る

共通ページ

無向グラフを二重頂点連結成分に分解し、成分と関節点を頂点とする Block Cut Tree を構築します。元のグラフが非連結なら森になります。

#include "lib/graph/block_cut_tree.hpp"

BlockCutTree graph(4);
graph.add_edge(0, 1);
graph.add_edge(1, 2);
graph.add_edge(2, 0);
graph.add_edge(2, 3);
graph.build();

assert(graph.block_count() == 2);
assert(graph.is_articulation(2));
assert(graph.node_id[0] == graph.node_id[1]);
const auto &forest = graph.tree; // vector<vector<int>> の無向森
assert(forest.size() == 3);
auto blocks = graph.groups();  // {0,1,2} と {2,3}(順序未指定)

頂点番号は 0 始まりです。多重辺・孤立点・非連結グラフ・空グラフに対応します。自己ループは分解に影響しません。橋の両端も 1 成分、孤立点も 1 頂点の成分とします。関節点は複数の成分に属します。

操作・メンバー 内容・返り値 計算量
BlockCutTree(n = 0) 元の頂点数を指定 \(O(1)\)
add_edge(u,v) 無向辺を追加し、0 始まりの辺 ID を返す 償却 \(O(1)\)
build() 成分・関節点・森を構築。辺を追加した後は再実行 \(O(n+m)\)
build_components() 成分・関節点・頂点の所属を構築。tree は空にする \(O(n+m)\)
block_count() / node_count() 成分数/森の頂点数 \(O(1)\)
group(c) 成分 c の元頂点を span<const int> で取得 \(O(1)\)
groups() 全成分の元頂点を vector<vector<int>> にコピー \(O(n)\)
articulation / is_articulation(v) 関節点の一覧/元頂点 v が関節点か \(O(1)\)
node_id[v] 元頂点 v に対応する森の頂点番号 \(O(1)\)
tree[t] 森の頂点 t の隣接頂点一覧 \(O(1)\)
is_block(t) 森の頂点 t が成分を表すか \(O(1)\)
vertex(t) 関節点の頂点なら元頂点番号、成分の頂点なら -1 \(O(1)\)
n / edges[id] 元の頂点数/辺の両端 \(O(1)\)

成分数を \(B\)、関節点数を \(A\) とすると、森の頂点番号は [0,B) が成分、[B,B+A) が関節点です。関節点の頂点は、その関節点を含む全ての成分の頂点と結ばれます。node_id[v] は、関節点ならその頂点、そうでなければ所属する成分の頂点を指します。

元の頂点 u,v の間を調べるときは、森の node_id[u],node_id[v] を使います。異なる木に属するなら元のグラフでも非連結です。木上の経路の内側にある関節点は、元のグラフで u,v を分離する頂点になります。

結果は build() または build_components() 後に参照してください。build_components() では tree は空になりますが、成分・関節点・頂点番号の問い合わせは利用できます。成分 ID と成分内の頂点順は未指定です。group() の参照は再構築・代入・破棄で無効になります。公開配列は直接書き換えず、辺の追加には add_edge() を使ってください。追加前に edges.reserve(m) で領域を予約できます。

頂点数は INT_MAX/2 以下、辺数は INT_MAX/2 以下とします。頂点数を \(n\)、辺数を \(m\) として、構築は \(O(n+m)\) 時間・空間、成分と森の保持は \(O(n)\) 空間です。

依存ライブラリ

検証コード

コード

#ifndef BLOCK_CUT_TREE_HPP
#define BLOCK_CUT_TREE_HPP

#include "../components/undirected_csr.hpp"
#include <algorithm>

class BlockCutTree {
	struct Frame { int v, next, low; };
	bool built_ = false;
	std::vector<int> vertices_, boundaries_;
	void check_([[maybe_unused]] int v) const { assert(built_ && 0 <= v && v < n); }

  public:
	int n;
	std::vector<std::pair<int, int>> edges;
	std::vector<int> node_id, articulation;
	std::vector<std::vector<int>> tree;

	explicit BlockCutTree(int n = 0) : n(n) {
		assert(0 <= n && n <= std::numeric_limits<int>::max() / 2);
	}
	int add_edge(int u, int v) {
		assert(0 <= u && u < n && 0 <= v && v < n);
		assert(edges.size() < std::size_t(std::numeric_limits<int>::max() / 2));
		built_ = false;
		int id = static_cast<int>(edges.size());
		edges.emplace_back(u, v);
		return id;
	}
	void build() { build_impl(true); }
	void build_components() { build_impl(false); }

  private:
	void build_impl(bool build_tree) {
		built_ = false;
		UndirectedVertexCSR graph(n, edges);
		std::vector<int> ord(n, -1), active;
		std::vector<Frame> stack;
		active.reserve(n), stack.reserve(n);
		vertices_.clear(), boundaries_.assign(1, 0);
		vertices_.reserve(n);
		node_id.assign(n, -1), articulation.clear(), tree.clear();
		int timer = 0;
		auto append = [&](int v, int block) {
			vertices_.push_back(v);
			node_id[v] = node_id[v] == -1 ? block : -2;
		};
		for (int root = 0; root < n; ++root) if (ord[root] == -1) {
			ord[root] = timer++;
			active.push_back(root);
			stack.push_back({root, graph.start[root], ord[root]});
			while (!stack.empty()) {
				auto& frame = stack.back();
				int v = frame.v;
				if (frame.next == graph.start[v + 1]) {
					int value = frame.low;
					stack.pop_back();
					if (stack.empty()) {
						active.pop_back();
						if (node_id[v] == -1) {
							append(v, static_cast<int>(boundaries_.size()) - 1);
							boundaries_.push_back(static_cast<int>(vertices_.size()));
						}
					} else {
						int p = stack.back().v;
						stack.back().low = std::min(stack.back().low, value);
						if (value >= ord[p]) {
							int block = static_cast<int>(boundaries_.size()) - 1, u;
							append(p, block);
							do {
								u = active.back();
								active.pop_back();
								append(u, block);
							} while (u != v);
							boundaries_.push_back(static_cast<int>(vertices_.size()));
						}
					}
					continue;
				}
				while (frame.next != graph.start[v + 1]) {
					int to = graph.adj[frame.next++];
					if (ord[to] != -1) frame.low = std::min(frame.low, ord[to]);
					else {
						ord[to] = timer++;
						active.push_back(to);
						stack.push_back({to, graph.start[to], ord[to]});
						break;
					}
				}
			}
		}
		int blocks = static_cast<int>(boundaries_.size()) - 1;
		for (int v = 0; v < n; ++v) if (node_id[v] == -2) {
			node_id[v] = blocks + static_cast<int>(articulation.size());
			articulation.push_back(v);
		}
		if (!build_tree) { built_ = true; return; }
		tree.resize(std::size_t(blocks) + articulation.size());
		std::vector<int> degree(tree.size());
		for (int c = 0; c < blocks; ++c)
			for (int i = boundaries_[c]; i < boundaries_[c + 1]; ++i) {
				int v = node_id[vertices_[i]];
				if (v >= blocks) ++degree[c], ++degree[v];
			}
		for (std::size_t v = 0; v < tree.size(); ++v) tree[v].reserve(degree[v]);
		for (int c = 0; c < blocks; ++c)
			for (int i = boundaries_[c]; i < boundaries_[c + 1]; ++i) {
				int v = node_id[vertices_[i]];
				if (v >= blocks) tree[c].push_back(v), tree[v].push_back(c);
			}
		built_ = true;
	}

  public:
	int block_count() const { assert(built_); return static_cast<int>(boundaries_.size()) - 1; }
	int node_count() const { assert(built_); return block_count() + static_cast<int>(articulation.size()); }
	bool is_articulation(int v) const { check_(v); return node_id[v] >= block_count(); }
	bool is_block(int node) const { assert(built_ && 0 <= node && node < node_count()); return node < block_count(); }
	int vertex(int node) const {
		assert(built_ && 0 <= node && node < node_count());
		return node < block_count() ? -1 : articulation[node - block_count()];
	}
	std::span<const int> group(int block) const {
		assert(built_ && 0 <= block && block < block_count());
		return {vertices_.data() + boundaries_[block], std::size_t(boundaries_[block + 1] - boundaries_[block])};
	}
	std::vector<std::vector<int>> groups() const {
		std::vector<std::vector<int>> result(block_count());
		for (int c = 0; c < block_count(); ++c) {
			auto members = group(c);
			result[c].assign(members.begin(), members.end());
		}
		return result;
	}
};

#endif
Back to top page