競技プログラミング用 C++ ライブラリ
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
#line 1 "data_structure/dynamic_lazy_segtree.hpp"
#line 1 "components/ordered_midpoint.hpp"
#include <type_traits>
template<class T>
constexpr T ordered_midpoint(T l, T r) {
static_assert(std::is_integral_v<T> && !std::is_same_v<T, bool> && sizeof(T) <= 8);
using U = std::make_unsigned_t<T>;
return T(U(l) + U(U(r) - U(l)) / 2);
}
#line 5 "data_structure/dynamic_lazy_segtree.hpp"
#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <limits>
#line 11 "data_structure/dynamic_lazy_segtree.hpp"
#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;
}
};