大嘘昔話
実は「segtreeというのはモノイドを載せられて、lazysegtreeはそれにいい感じの作用が行えて…」のようにsegtreeが抽象化されたのは割と最近です。 昔はなんかsegtreeって大体実装一緒だな…と思いながら、みな自分のstarryskytree.cppを毎回コピペして適当に書き直して使っていました。
もちろん適切にクラス等を使って最強のsegtreeを作れば勝ちまくりモテまくりであることには皆薄々気づいており、私もそのような夢を追い求める若者の一人でした。 その名残がこちらです。
2023年
時は2023年、昔はC++11の機能は新しいといわれていましたが、今ではC++20がどこのジャッジでも使えます(正確には使えないジャッジは引退しました)。C++20と言えばconcept、今回は昔を思いながら、conceptの勉強がてら抽象化segtreeに挑戦してみました。
上の遅延SegTree / 遅延Segtree2にあるように、大体実装方法は
struct
を自分で定義してそれをsegtreeに渡す- lambda/関数 を演算の個数だけ用意して一気にsegtreeに渡す
の2種類だと思うんですが、今回は両方できるようにしてみました。2018年ならいざ知らず同様の実装がそこら中にあると思う。
コード
こちらです。
#include <vector> #include <iostream> #include <numeric> template <class T> concept monoid = requires (T& x, typename T::S s) { { x.op(s, s) } -> std::same_as<typename T::S>; { x.e() } -> std::same_as<typename T::S>; }; template <monoid M> struct SegTree { using S = M::S; M m; std::vector<S> v; SegTree(M _m, std::vector<S> _v) : m(_m), v(_v) { } S all_prod() { // TODO optimize :) S val = m.e(); for (auto x : v) { val = m.op(val, x); } return val; } }; template <class T, class OP, class E> struct LambdaMonoid { using S = T; S op(S a, S b) { return _op(a, b); } S e() { return _e(); } LambdaMonoid(OP op, E e) : _op(op), _e(e) {} private: OP _op; E _e; }; template <class OP, class E> LambdaMonoid(OP op2, E e2)->LambdaMonoid<decltype(e2()), OP, E>; // --- ここまでライブラリ --- struct AddInt { using S = int; S op(S a, S b) { return a + b; } S e() { return S(0); } }; int main() { std::vector<int> v(10); std::iota(v.begin(), v.end(), 0); SegTree seg0(AddInt(), v); SegTree seg1( LambdaMonoid([&](int a, int b) { return a + b; }, [&]() { return 0; }), v); std::cout << "sum(1..10) = " << seg0.all_prod() << std::endl; std::cout << "sum(1..10) = " << seg1.all_prod() << std::endl; return 0; }
タイトルに遅延segtreeとありますが、シンプルに詐欺です。普通のsegtreeしか試してみていません。
解説
まず、最初の数行がいきなり重要です
template <class T> concept monoid = requires (T& x, typename T::S s) { { x.op(s, s) } -> std::same_as<typename T::S>; { x.e() } -> std::same_as<typename T::S>; }; template <monoid M> struct SegTree { using S = M::S; M m; :
これは、monoid
conceptを定義し、struct SegTree
がこのmonoid
conceptを満たすM
しか受け取れないようにしています。M
がmonoid
conceptを満たすとは、
using S = hoge
として値の型が定義されているop(S, S) -> S
をメンバとして持つe() -> S
をメンバとして持つ
という、大体atcoder libraryと同じ定義です。例えば下のほうにあるstruct AddInt
がmonoid
conceptを満たします。
struct AddInt { using S = int; S op(S a, S b) { return a + b; } S e() { return S(0); } };
なので、こういうAddInt構造体を用意して、main
関数の最初で行われているように
std::vector<int> v(10); std::iota(v.begin(), v.end(), 0); SegTree seg0(AddInt(), v);
と書けば[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
の格納されたsegtreeが出来ます。
また、わざわざstruct AddInt
のようにstructを定義しなくても、ラムダ式を書き並べるだけでsegtreeを使うこともできます。
SegTree seg1( LambdaMonoid([&](int a, int b) { return a + b; }, [&]() { return 0; }), v);
これがどういう仕組みかというと、ラムダ式を受け取ってmonoid
structのようにふるまうLambdaMonoid
もライブラリ側に用意しておきました。
template <class T, class OP, class E> struct LambdaMonoid { using S = T; S op(S a, S b) { return _op(a, b); } S e() { return _e(); } LambdaMonoid(OP op, E e) : _op(op), _e(e) {} private: OP _op; E _e; }; template <class OP, class E> LambdaMonoid(OP op2, E e2)->LambdaMonoid<decltype(e2()), OP, E>;
応用編として、「pair<S, S>
を使うことで一般のモノイドをReverse可能なモノイドに変換するやつ」とかが実現できると思っています。なんのこっちゃという話ですが、平衡二分木でこういうのが欲しくなります。
template <monoid M> struct AttachReverse { using S = std::pair<M::S, M::S>; S op(S a, S b) { return {m.op(a, b), m.op(b, a)}; } S e() { return {m.e(), m.e()}; } S rev(S a) { return {a.second, a.first}; } AttachReverse(M _m) : m(_m) {} private: M m; };
結論
全てが懐古