cp-library

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

View the Project on GitHub qandori/cp-library

:heavy_check_mark: Dynamic Lazy Segtree

data_structure/dynamic_lazy_segtree.hpp · GitHub で見る

共通ページ

巨大な整数座標の列に対する区間更新と区間集約を扱います。区間は全て半開区間です。更新で必要になったノードだけを作り、座標を事前に集めずに使えます。取得と境界探索ではノードを作成せず、内部状態も変更しません。

次は、最初は全要素が 0 の列に対する区間加算・区間和です。S に和と区間の長さを保持し、initial(l,r) で未更新区間の集約値を返します。

#include "lib/data_structure/dynamic_lazy_segtree.hpp"

struct S { long long sum, length; };
S op(S a, S b) { return {a.sum + b.sum, a.length + b.length}; }
S e() { return {0, 0}; }
S mapping(long long f, S x) { return {x.sum + f * x.length, x.length}; }
long long composition(long long f, long long g) { return f + g; }
long long id() { return 0; }
S initial(std::int64_t l, std::int64_t r) { return {0, r - l}; }

using Seg = DynamicLazySegtree<S, op, e, long long, mapping, composition, id, initial>;

int main() {
	Seg seg(-1000000000000LL, 1000000000000LL);
	seg.apply(-3, 5, 2);
	seg.set(1, S{9, 1});
	auto sum = seg.prod(-3, 5).sum; // 23
	auto x = seg.get(1).sum;       // 9
	auto r = seg.max_right(-3, [](S x) { return x.sum <= 6; }); // 0
	(void)sum; (void)x; (void)r;
}

テンプレート引数は DynamicLazySegtree<S,op,e,F,mapping,composition,id,initial,Coord> です。

引数 内容
S / op(a,b) / e() 区間の集約値、その結合、空区間の単位元。op は結合的で、左から右の順に結合する
F / mapping(f,x) 更新と、その更新を集約値 x に作用させた結果
composition(f,g) / id() g の後に f を行う合成と、何もしない更新
initial(l,r) 未更新の区間 [l,r) の集約値。省略すると e() を使う
Coord 座標型。既定は std::int64_t。bool を除く 64 bit 以下の整数型

mapping(id(),x)=x、mapping(composition(f,g),x)=mapping(f,mapping(g,x))、mapping(f,op(x,y))=op(mapping(f,x),mapping(f,y)) が必要です。op や更新同士が可換である必要はありません。S・F はコピー可能にしてください。デフォルト構築や等値比較は不要です。

initial は、隣接区間について op(initial(l,m),initial(m,r))=initial(l,r) を満たすように定義します。全要素が同じ値でなくても、この関数で集約値を返せれば扱えます。e() は空区間の値なので、区間和で長さを持つ場合の initial(l,r) の代わりにはなりません。上の例で初期値を全て 1 にするなら return {r-l,r-l}; とします。

一点代入の S も、他の区間と結合できる値を渡します。上の区間和では長さを必ず 1 にします。初期列を渡す場合も、各要素は S{値,1} です。build(a) は座標範囲を [0,a.size()) に変更します。clear() は、構築時の配列ではなく initial が表す状態に戻します。

max_right(l,f) は、f(prod(l,r)) が真になる最大の r を返します。min_left(r,f) は、f(prod(l,r)) が真になる最小の l を返します。f(e()) が真で、区間を伸ばすと真から偽へ単調に変化する必要があります。区間和を上限と比較する場合は、各要素が非負であることが必要です。

構築範囲の右端は Coord で表せる必要があります。例えば std::int64_t の最大値そのものを要素の座標に含めたい場合、その次の右端を表せないため、座標型や座標の取り方を変えてください。分割・サイズ取得では符号付きの差のオーバーフローを避けていますが、initial や利用者の演算にも十分な範囲の型を使ってください。

構築範囲の長さを \(W\)、配列の長さを \(n\)、作成済みノード数を \(k\) とします。各演算と initial を \(O(1)\) とした計算量です。空・長さ 1 の範囲では \(O(1)\)、64 bit 座標でも木の深さは最大 64 です。

操作 内容・返り値 計算量
DynamicLazySegtree(n = 0) / DynamicLazySegtree(l,r) [0,n) / [l,r) を initial で初期化。作成済みノードは 0 個 \(O(1)\)
DynamicLazySegtree(a) / build(a) vector<S> から [0,a.size()) を構築。非空なら \(2n-1\) ノードを作る \(O(n)\)、再構築時は旧ノードの破棄も必要
apply(l,r,f) / apply(p,f) 区間 / 一点に更新 f を適用 償却 \(O(\log W)\)
set(p,x) 一点を x に代入 償却 \(O(\log W)\)
prod(l,r) / get(p) 区間集約 / 一点の値を返す。const で呼べる \(O(\log W)\)
all_prod() 全体の集約値。空なら e() \(O(1)\)
max_right(l,f) / min_left(r,f) 条件を満たす最大の右端 / 最小の左端。const で呼べる \(O(\log W)\)
size() / empty() 範囲の長さを Coord の符号なし型で返す / 空かどうか \(O(1)\)
left_bound() / right_bound() 構築範囲の左端 / 右端 \(O(1)\)
node_count() / memory_usage() 作成済みノード数 / 確保済みノード領域のバイト数 \(O(1)\)
reserve(count) ノード count 個分の領域を予約。更新回数ではなくノード数を指定 容量拡張時は \(O(k)\)
clear() 容量と座標範囲を残して initial の状態に戻す \(O(k)\)
swap(other) 内容と座標範囲を交換 \(O(1)\)
コピー / ムーブ 独立した複製 / 内容の移動。移動元は同じ範囲の初期状態になる コピー \(O(k)\)、ムーブ構築 \(O(1)\)、ムーブ代入は旧ノードの破棄も必要

空区間の更新は何もせず、集約は e() です。更新で作るノードは 1 操作につき \(O(\log W)\) 個、クエリでは 0 個です。空間は \(O(k)\)、予約容量が大きい場合はその容量分です。ノード数は INT_MAX-1 以下にしてください。

依存ライブラリ

検証コード

コード

#ifndef DYNAMIC_LAZY_SEGTREE_HPP
#define DYNAMIC_LAZY_SEGTREE_HPP

#include "../components/ordered_midpoint.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#include <type_traits>
#include <utility>
#include <vector>

template<class S, auto op, auto e, class F, auto mapping, auto composition, auto id,
	auto initial = nullptr, class Coord = std::int64_t>
class DynamicLazySegtree {
	static_assert(std::is_integral_v<Coord> && !std::is_same_v<Coord, bool> && sizeof(Coord) <= 8);
	using U = std::make_unsigned_t<Coord>;
	struct Node {
		S value;
		F lazy;
		int left = 0, right = 0;
		bool dirty = false;
	};
	std::vector<Node> nodes{Node{e(), id()}};
	int root = 0;
	Coord first, last;
	static bool leaf(Coord l, Coord r) { return U(U(r) - U(l)) == 1; }
	static S base(Coord l, Coord r) {
		if (l == r) return e();
		if constexpr (std::is_same_v<decltype(initial), std::nullptr_t>) return e();
		else return initial(l, r);
	}
	S value(int k, Coord l, Coord r) const { return k ? nodes[k].value : base(l, r); }
	int make_node(Coord l, Coord r) {
		assert(nodes.size() < std::size_t(std::numeric_limits<int>::max()));
		nodes.push_back(Node{base(l, r), id()});
		return static_cast<int>(nodes.size() - 1);
	}
	void all_apply(int k, const F &f, bool leaf) {
		nodes[k].value = mapping(f, std::move(nodes[k].value));
		if (!leaf) {
			nodes[k].lazy = nodes[k].dirty ? composition(f, nodes[k].lazy) : f;
			nodes[k].dirty = true;
		}
	}
	void push(int k, Coord l, Coord m, Coord r) {
		if (!nodes[k].dirty) return;
		F f = nodes[k].lazy;
		if (!nodes[k].left) nodes[k].left = make_node(l, m);
		if (!nodes[k].right) nodes[k].right = make_node(m, r);
		all_apply(nodes[k].left, f, leaf(l, m));
		all_apply(nodes[k].right, f, leaf(m, r));
		nodes[k].dirty = false;
	}
	void pull(int k, Coord l, Coord m, Coord r) {
		nodes[k].value = op(value(nodes[k].left, l, m), value(nodes[k].right, m, r));
	}
	int update(int k, Coord l, Coord r, Coord a, Coord b, const F &f) {
		if (!k) k = make_node(l, r);
		if (a == l && b == r) { all_apply(k, f, leaf(l, r)); return k; }
		Coord m = ordered_midpoint(l, r);
		push(k, l, m, r);
		if (a < m) nodes[k].left = update(nodes[k].left, l, m, a, std::min(b, m), f);
		if (m < b) nodes[k].right = update(nodes[k].right, m, r, std::max(a, m), b, f);
		pull(k, l, m, r);
		return k;
	}
	int write(int k, Coord l, Coord r, Coord p, const S &x) {
		if (!k) k = make_node(l, r);
		if (leaf(l, r)) { nodes[k].value = x; return k; }
		Coord m = ordered_midpoint(l, r);
		push(k, l, m, r);
		if (p < m) nodes[k].left = write(nodes[k].left, l, m, p, x);
		else nodes[k].right = write(nodes[k].right, m, r, p, x);
		pull(k, l, m, r);
		return k;
	}
	S product(int k, Coord l, Coord r, Coord a, Coord b) const {
		if (!k) return base(a, b);
		if (a == l && b == r) return nodes[k].value;
		Coord m = ordered_midpoint(l, r);
		S result = b <= m ? product(nodes[k].left, l, m, a, b) :
			m <= a ? product(nodes[k].right, m, r, a, b) :
			op(product(nodes[k].left, l, m, a, m), product(nodes[k].right, m, r, m, b));
		return nodes[k].dirty ? mapping(nodes[k].lazy, std::move(result)) : result;
	}
	template<bool Right, class Pred>
	bool search(int k, Coord l, Coord r, Coord bound, S &acc, const F *carry, Pred &f, Coord &answer) const {
		if ((Right ? bound <= l : r <= bound)) {
			S part = value(k, l, r);
			if (carry) part = mapping(*carry, std::move(part));
			S next = Right ? op(acc, part) : op(part, acc);
			if (f(next)) { acc = std::move(next); return false; }
			if (leaf(l, r)) { answer = Right ? l : r; return true; }
		}
		Coord m = ordered_midpoint(l, r);
		auto descend = [&](const F *pending) {
			int left = k ? nodes[k].left : 0, right = k ? nodes[k].right : 0;
			if constexpr (Right) {
				if (bound < m && search<Right>(left, l, m, bound, acc, pending, f, answer)) return true;
				return search<Right>(right, m, r, bound, acc, pending, f, answer);
			} else {
				if (m < bound && search<Right>(right, m, r, bound, acc, pending, f, answer)) return true;
				return search<Right>(left, l, m, bound, acc, pending, f, answer);
			}
		};
		if (k && nodes[k].dirty) {
			F pending = carry ? composition(*carry, nodes[k].lazy) : nodes[k].lazy;
			return descend(&pending);
		}
		return descend(carry);
	}
	int build_values(const std::vector<S> &a, Coord l, Coord r) {
		int k = make_node(l, r);
		if (leaf(l, r)) nodes[k].value = a[std::size_t(l)];
		else {
			Coord m = ordered_midpoint(l, r);
			int left = build_values(a, l, m), right = build_values(a, m, r);
			nodes[k].left = left;
			nodes[k].right = right;
			pull(k, l, m, r);
		}
		return k;
	}
	void check([[maybe_unused]] Coord l, [[maybe_unused]] Coord r) const {
		assert(first <= l && l <= r && r <= last);
	}

  public:
	using value_type = S;
	using update_type = F;
	using coordinate_type = Coord;
	explicit DynamicLazySegtree(Coord n = 0) : DynamicLazySegtree(0, n) {}
	DynamicLazySegtree(Coord l, Coord r) : first(l), last(r) { assert(l <= r); }
	explicit DynamicLazySegtree(const std::vector<S> &a) : first(0), last(0) { build(a); }
	DynamicLazySegtree(const DynamicLazySegtree &) = default;
	DynamicLazySegtree &operator=(const DynamicLazySegtree &) = default;
	DynamicLazySegtree(DynamicLazySegtree &&other) : DynamicLazySegtree(other.first, other.last) { swap(other); }
	DynamicLazySegtree &operator=(DynamicLazySegtree &&other) {
		if (this != &other) { DynamicLazySegtree moved(std::move(other)); swap(moved); }
		return *this;
	}
	void swap(DynamicLazySegtree &other) noexcept {
		nodes.swap(other.nodes);
		std::swap(root, other.root);
		std::swap(first, other.first);
		std::swap(last, other.last);
	}
	void build(const std::vector<S> &a) {
		assert(a.size() <= std::uintmax_t(std::numeric_limits<Coord>::max()));
		assert(a.size() < std::size_t(std::numeric_limits<int>::max()) / 2);
		clear();
		first = 0;
		last = static_cast<Coord>(a.size());
		reserve(a.empty() ? 0 : 2 * a.size() - 1);
		if (!a.empty()) root = build_values(a, first, last);
	}
	void reserve(std::size_t count) {
		assert(count < std::size_t(std::numeric_limits<int>::max()));
		nodes.reserve(count + 1);
	}
	void clear() { nodes.erase(nodes.begin() + 1, nodes.end()); root = 0; }
	U size() const { return U(last) - U(first); }
	bool empty() const { return first == last; }
	Coord left_bound() const { return first; }
	Coord right_bound() const { return last; }
	std::size_t node_count() const { return nodes.size() - 1; }
	std::size_t memory_usage() const { return nodes.capacity() * sizeof(Node); }
	S all_prod() const { return value(root, first, last); }
	S prod(Coord l, Coord r) const { check(l, r); return l == r ? e() : product(root, first, last, l, r); }
	S get(Coord p) const { assert(first <= p && p < last); return prod(p, Coord(p + 1)); }
	void set(Coord p, const S &x) { assert(first <= p && p < last); root = write(root, first, last, p, x); }
	void apply(Coord l, Coord r, const F &f) {
		check(l, r);
		if (l != r) root = update(root, first, last, l, r, f);
	}
	void apply(Coord p, const F &f) { assert(first <= p && p < last); apply(p, Coord(p + 1), f); }
	template<class Pred>
	Coord max_right(Coord l, Pred f) const {
		assert(first <= l && l <= last && f(e()));
		Coord answer = last;
		if (l != last) { S acc = e(); search<true>(root, first, last, l, acc, nullptr, f, answer); }
		return answer;
	}
	template<class Pred>
	Coord min_left(Coord r, Pred f) const {
		assert(first <= r && r <= last && f(e()));
		Coord answer = first;
		if (r != first) { S acc = e(); search<false>(root, first, last, r, acc, nullptr, f, answer); }
		return answer;
	}
};

#endif
Back to top page