cp-library

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

View the Project on GitHub qandori/cp-library

:heavy_check_mark: Static Top Tree

tree/static_top_tree.hpp · GitHub で見る

共通ページ

木の形を固定したまま、頂点・辺の値の変更に合わせて木 DP を更新します。構築済みの HLD と、DP の型・演算を渡します。

#include "lib/tree/static_top_tree.hpp"

struct SumDP {
	using Vertex = long long;
	using Point = long long;
	using Path = long long;
	static Point point_identity() { return 0; }
	static Path path_identity() { return 0; }
	static Point rake(Point a, Point b) { return a + b; }
	static Path compress(Path a, Path b) { return a + b; }
	static Point add_edge(Path a) { return a; }
	static Path add_vertex(Point a, Vertex v) { return a + v; }
};

HLD tree(3);
tree.add_edge(0, 1);
tree.add_edge(1, 2);
tree.build(0);
StaticTopTree<SumDP> dp(tree, {1, 2, 3});
dp.set(1, 4);
long long total = dp.dp();                  // 8
long long subtree = dp.subtree_prod(1);     // 7
操作 内容・返り値 計算量
StaticTopTree<DP>() 空の木を作る \(O(1)\)
StaticTopTree<DP>(tree,values) 構築済みの HLD と頂点番号順の vector<Vertex> で構築 \(O(n\log n)\)
build(tree,values) 木と値を置き換えて再構築 \(O(n\log n)\)
size() / empty() 頂点数/空か \(O(1)\)
root() 構築時の根。空なら -1 \(O(1)\)
parent(v) 元の木の親。根では -1 \(O(1)\)
edge_vertex(e) HLD に追加した辺 ID e の子側の頂点 \(O(1)\)
get(v) 頂点 v の const Vertex& \(O(1)\)
set(v,value) 頂点の値を変更し、必要なクラスタを更新 \(O(\log n)\)
all_prod() 木全体の Path。境界のパスは構築時の根から heavy path の末尾まで \(O(1)\)
dp() DP::add_edge(all_prod()) による木全体の Point \(O(1)\)
subtree_prod(v) 構築時の根に対する v の部分木の Path。境界のパスは v から heavy path の末尾まで \(O(\log n)\)
prod(v) 木全体を、構築時の根から v へのパスに沿って集約した Path \(O(\log n)\)
Vertex / Point / Path DP に指定した型の別名 —(型)

頂点は 0 始まりです。空の木・1 頂点の木も扱います。values.size() は頂点数と一致させます。構築時に必要な情報をコピーするため、元の HLD は構築後に破棄できます。辺の追加・削除には再構築が必要です。get の参照は再構築で無効になります。

DP には次の型と static 関数を定義します。値はコピー・代入可能な型にします。

型・関数 意味 計算量
DP::Vertex 頂点の情報。必要なら親辺の情報も含める —(型)
DP::Point 同じ親へ接続する枝の集約 —(型)
DP::Path 境界のパスを持つクラスタの集約 —(型)
DP::point_identity() 枝がない場合の Point DP による(定数時間を仮定)
DP::path_identity() 空のパスの Path DP による(定数時間を仮定)
DP::rake(a,b) 同じ親へ接続する枝を併合 DP による(定数時間を仮定)
DP::compress(a,b) 上側のパス a と下側のパス b をこの順で連結 DP による(定数時間を仮定)
DP::add_edge(path) パスクラスタを親へ接続する枝の Point に変換 DP による(定数時間を仮定)
DP::add_vertex(point,vertex) 枝の集約に頂点を加え、長さ 1 の Path に変換 DP による(定数時間を仮定)

rake は可換モノイド、compress はモノイドにします。add_edge(path_identity()) == point_identity() が必要です。クラスタの併合順序によらず同じ木 DP を表すように各演算を定義します。逆元は不要で、積が 0 になる DP も扱えます。

辺を更新する場合は、子の Vertex に親辺の値を保存し、edge_vertex(e) で子を求めて set します。根の仮想的な親辺は、DP に応じた単位元にします。subtree_prod(v) にも Vertex に保存した v の親辺の作用が含まれます。

prod(v) は経路上の頂点だけを集約する操作ではありません。経路外の枝を Point として畳み、全頂点を含む根から v へのクラスタを返します。Path に両方向の情報を持たせれば、指定した頂点を根とする全方位の値も取り出せます。逆方向の辺・頂点の作用も DP 側で定義します。一次関数合成の全方位 DP と 可変根の使用例 を参照してください。独立集合のように枝の方向を反転させる処理が必要な DP では、固定根の Path をそのまま逆順にするだけでは全方位の答えになりません。

all_prod・subtree_prod も Path を返すため、固定根の答えとして使う際には DP に応じた取り出し方を使います。例えば行列を Path にする DP では、末尾に空の子の状態を作用させます。dp はその処理を add_edge で行う場合に使えます。

DP の型・演算を定数時間・定数空間とすると、HLD 構築後の構築・再構築は \(O(n\log n)\) 時間、空間は \(O(n)\) です。set・subtree_prod・prod は \(O(\log n)\)、それ以外の操作は \(O(1)\) です。

依存ライブラリ

検証コード

コード

#ifndef STATIC_TOP_TREE_HPP
#define STATIC_TOP_TREE_HPP

#include <cassert>
#include <utility>
#include <vector>
#include "../components/static_top_tree_structure.hpp"

template <class DP>
class StaticTopTree {
	using Structure = static_top_tree_detail::Structure;
	using PathValue = typename DP::Path;
	using PointValue = typename DP::Point;
	struct Link { int index = -1; Structure::Kind kind = Structure::Vertex; bool right = false; };
	struct VertexNode { typename DP::Vertex info; PointValue light; Link up; };
	struct PathNode { PathValue child[2]; Link up; };
	struct PointNode { PointValue child[2]; Link up; };
	std::vector<VertexNode> vertices_;
	std::vector<PathNode> paths_;
	std::vector<PointNode> points_;
	std::vector<int> parent_, edge_child_;
	PathValue all_ = DP::path_identity();
	int root_ = -1;
	void check([[maybe_unused]] int v) const { assert(0 <= v && v < size()); }

  public:
	using Vertex = typename DP::Vertex;
	using Path = typename DP::Path;
	using Point = typename DP::Point;
	StaticTopTree() = default;
	StaticTopTree(const HLD& tree, std::vector<Vertex> values) { build(tree, std::move(values)); }
	void build(const HLD& tree, std::vector<Vertex> values) {
		assert(int(values.size()) == tree.n);
		Structure structure(tree);
		int n = tree.n;
		vertices_.clear(), vertices_.reserve(n);
		for (auto& value : values) vertices_.push_back({std::move(value), DP::point_identity(), {}});
		paths_.assign(structure.paths - n, PathNode{{DP::path_identity(), DP::path_identity()}, {}});
		points_.assign(structure.points, PointNode{{DP::point_identity(), DP::point_identity()}, {}});
		std::vector<Path> paths(structure.paths, DP::path_identity());
		std::vector<Point> points(structure.points, DP::point_identity());
		auto point = [&](int id) {
			if (id == -1) return DP::point_identity();
			const auto& node = structure.nodes[id];
			return node.kind == Structure::Rake ? points[node.index] : DP::add_edge(paths[node.index]);
		};
		for (int id : structure.order) {
			const auto& node = structure.nodes[id];
			Link up;
			if (node.parent != -1) {
				const auto& p = structure.nodes[node.parent];
				up = {p.kind == Structure::Compress ? p.index - n : p.index, p.kind, p.right == id};
			}
			if (node.kind == Structure::Vertex) {
				auto& v = vertices_[id];
				v.light = point(node.left), v.up = up;
				paths[id] = DP::add_vertex(v.light, v.info);
			} else if (node.kind == Structure::Compress) {
				auto& v = paths_[node.index - n];
				v = {{paths[structure.nodes[node.left].index], paths[structure.nodes[node.right].index]}, up};
				paths[node.index] = DP::compress(v.child[0], v.child[1]);
			} else {
				auto& v = points_[node.index];
				v = {{point(node.left), point(node.right)}, up};
				points[node.index] = DP::rake(v.child[0], v.child[1]);
			}
		}
		all_ = n ? paths[structure.nodes[structure.root].index] : DP::path_identity();
		root_ = structure.root_vertex;
		parent_ = std::move(structure.parent), edge_child_ = std::move(structure.edge_child);
	}
	int size() const { return int(vertices_.size()); }
	bool empty() const { return vertices_.empty(); }
	int root() const { return root_; }
	int parent(int v) const { check(v); return parent_[v]; }
	int edge_vertex(int e) const {
		assert(0 <= e && e < int(edge_child_.size()));
		return edge_child_[e];
	}
	const Vertex& get(int v) const { check(v); return vertices_[v].info; }
	void set(int v, const Vertex& value) {
		check(v);
		auto& vertex = vertices_[v];
		vertex.info = value;
		Path path = DP::add_vertex(vertex.light, vertex.info);
		Link up = vertex.up;
		while (up.index != -1) {
			while (up.index != -1 && up.kind == Structure::Compress) {
				auto& node = paths_[up.index];
				node.child[up.right] = path;
				path = DP::compress(node.child[0], node.child[1]), up = node.up;
			}
			if (up.index == -1) break;
			Point point = DP::add_edge(path);
			while (up.kind == Structure::Rake) {
				auto& node = points_[up.index];
				node.child[up.right] = point;
				point = DP::rake(node.child[0], node.child[1]), up = node.up;
			}
			auto& node = vertices_[up.index];
			node.light = point;
			path = DP::add_vertex(point, node.info), up = node.up;
		}
		all_ = path;
	}
	Path all_prod() const { return all_; }
	Point dp() const { return DP::add_edge(all_); }
	Path subtree_prod(int v) const {
		check(v);
		const auto& vertex = vertices_[v];
		Path result = DP::add_vertex(vertex.light, vertex.info);
		Link up = vertex.up;
		while (up.index != -1 && up.kind == Structure::Compress) {
			const auto& node = paths_[up.index];
			if (!up.right) result = DP::compress(result, node.child[1]);
			up = node.up;
		}
		return result;
	}
	Path prod(int v) const {
		check(v);
		Point light = vertices_[v].light;
		Path result = DP::path_identity();
		Link up = vertices_[v].up;
		while (true) {
			Path above = DP::path_identity(), below = DP::path_identity();
			while (up.index != -1 && up.kind == Structure::Compress) {
				const auto& node = paths_[up.index];
				if (!up.right) below = DP::compress(below, node.child[1]);
				else above = DP::compress(node.child[0], above);
				up = node.up;
			}
			light = DP::rake(light, DP::add_edge(below));
			result = DP::compress(above, DP::compress(DP::add_vertex(light, vertices_[v].info), result));
			if (up.index == -1) return result;
			light = DP::point_identity();
			while (up.kind == Structure::Rake) {
				const auto& node = points_[up.index];
				if (!up.right) light = DP::rake(light, node.child[1]);
				else light = DP::rake(node.child[0], light);
				up = node.up;
			}
			v = up.index, up = vertices_[v].up;
		}
	}
};

#endif
Back to top page