実装方法ではなく使い方のメモです。 計算する区間の添字の流儀は AtCoder Library を参考にしています。

まだ整理できていないもの

  • 双対セグメント木
  • 2Dセグ木
  • 永続セグメント木
  • imos法?

フェニック木(Fenwick Tree, Bit Indexed Tree)

一点加算更新・区間加算 のクエリが処理できるデータ構造。

できることは限られているが、同じ処理をセグメント木や遅延セグメント木で行うときよりも高速に動作し、実装も比較的シンプル。

処理できるクエリ

a={ai}i=0n1a = \{a_i\}_{i=0}^{n-1} を長さが nn で初期値が全て 00 の配列とする。

  1. (一点加算更新) インデックス ii と値 xx が与えられたとき、配列を aiai+xa_i \coloneqq a_i + x と更新する。計算量 O(logn)O(\log n)
  2. (区間加算) インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の和 i=lr1ai\sum_{i=l}^{r-1} a_i を計算する。計算量 O(logn)O(\log n)

具体例・例題

セグメント木(Segment Tree)

一点更新・区間取得 のクエリが処理できるデータ構造。

処理できるクエリ

セグメント木で扱う値の集合は、モノイドである必要がある。 S=(S,,e)S = (S, \cdot, e) をモノイド、 a={ai}i=0n1a = \{a_i\}_{i=0}^{n-1} を長さが nn で初期値が全て ee の配列とする。

  1. (一点更新) インデックス ii と値 xSx \in S が与えられたとき、配列を ai=xa_i = x と更新する。計算量 O(logn)O(\log n)
  2. (区間取得) インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の(モノイドの)積 i=lr1ai\prod_{i=l}^{r-1} a_i を計算する。計算量 O(logn)O(\log n)

また、O(1)O(1)aia_i の値を一点取得できる。

具体例・例題

  • AtCoder Library Practice Contest J - Segment Tree

  • (Fenwick Tree) モノイド SS の演算を加算 ++ とすると、Fenwick Treeになる。S=(Z,0,+)S = (\mathbb{Z}, 0, +) とすると、整数の配列のFenwickTree。一点更新/一点加算更新は適切に読み替える必要がある。

  • (Range Minimum Query) S=(Z{},min,)S=(\mathbb{Z} \cup \{\infty\}, \min, \infty) とおくと、区間取得として以下を実現する Range Minimum Query となる。

    • インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の最小値 mini=lr1{ai}\min_{i=l}^{r-1} \{a_i\} を計算する。
  • (BitXor) AtCoder Beginner Contest 185 F - Range Xor Query S=(Z,,0)S=(\mathbb{Z}, \veebar, 0) とすると XOR が処理できる。

  • (BitOr) S=(Z,,0)S=(\mathbb{Z}, \lor, 0) とすると OR が処理できる。

  • (区間加算・一点取得) セグメント木を利用して、区間加算・一点取得のクエリを処理できる。

    1. (区間加算) インデックスの組 (l,r)(l, r) と値 xx が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素に xx を加算する。つまり、i[l,r)i \in [l, r) に対して ai=ai+xa_i = a_i + x と更新する。
    2. (一点取得) aia_i の値を取得する。

    これは、前項との差分の数列 bib_i に対してモノイド S=(Z,0,+)S = (\mathbb{Z}, 0, +) を考慮したセグメント木を作成して管理する、という考え方になる。区間加算については bl=bl+x,br=brxb_l = b_l + x, b_r = b_r - x と 一点取得・一点更新を2回実施し、一点取得に対しては、k=0ibk\sum_{k=0}^{i}b_k を求めるため、区間 [0,i+1)[0, i+1) の区間取得を実施すれば良い。(imos法の考え方と似ている)

遅延セグメント木(Lazy Segment Tree)

通常のセグメント木の一点更新は点を含む全ての区間の値を更新するので、区間更新の計算量が多い。 そのため二分木を二つ使って区間取得操作や二度目の更新操作の時まで値の更新を遅延させる。

これにより、区間更新・区間取得 のクエリが処理できる。

処理できるクエリ

遅延セグメント木でもセグメント木と同様に管理する値の集合がモノイドであることを仮定する。 さらに遅延セグメント木では、値の更新処理のクエリも二分木を使って蓄積管理していく対象でもある。 よって、二種類のモノイド構造を考えることになる。

  • (セグメント木上に乗せるデータ) モノイド S=(S,S,eS)S = (S, \cdot_S, e_S)
  • (遅延伝播させるデータ) モノイド X=(X,X,eX)X = (X, \cdot_X, e_X)

さらに、更新処理を行うモノイド XX が 更新される側のモノイド SS との関係を考えるために、

  • モノイド XX の モノイド SS に対する 右作用 :S×XS* : S \times X \rightarrow S

が定義されているとする。このとき、

  1. (区間更新) インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素に xx を作用させる。つまり、i[l,r)i \in [l, r) に対して ai=aixa_i = a_i * x と更新する。計算量 O(logn)O(\log n)
  2. (区間取得) インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の(モノイドSに定義された)積 i=lr1ai\prod_{i=l}^{r-1} a_i を計算する。計算量 O(logn)O(\log n)

の二つのクエリが処理できる。aia_i の値を一点取得する場合はセグメント木とは違い O(logn)O(\log n) かかる。

具体例・例題

  • (セグメント木) セグメント木は遅延セグメント木で次のように表現できる。
    • S=(S,S,eS)S = (S, \cdot_S, e_S) をセグメント木で用いているモノイドとする。
    • X={idS}{ft:SS,st,tS}X = \{id_S\} \cup \{f_t: S \rightarrow S, s \mapsto t, t\in S\} という SS から SS への写像からなる集合を考え、X\cdot_X は写像の合成、 eX=idSe_X = id_S とすると、(X,X,eX)(X, \cdot_X, e_X) はセグメント木の代入に対応するモノイドとなる。
    • セグメント木では一点更新・区間取得であったが、遅延セグメント木に上の構造を入れると区間更新・区間取得ができるようになる。
  • (区間アフィン更新・区間取得) AtCoder Library Practice Contest K - Range Affine Range Sum
    計算対象は整数列。(正確には p=998244353p=998244353 とした時の (Z/pZ,+,0)(\mathbb{Z}/p\mathbb{Z}, +, 0) を考えている)
    1. インデックスの組 (l,r)(l, r) と値 b,cb, c が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素をアフィン変換する。つまり、 i[l,r)i \in [l, r) に対して ai=b×ai+ca_i = b \times a_i + c と更新する。
    2. インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の和 i=1r1ai\sum_{i=1}^{r-1}a_i を計算する。

b×(b×a+c)+c=bb×a+(bc+c)b' \times (b \times a + c) + c' = bb' \times a + (b'c + c') だから、X=(Z×Z,,(1,0)),(b,c)(b,c)=(bb,bc+c)X=(\mathbb{Z} \times \mathbb{Z}, \cdot, (1,0)), (b', c') \cdot (b, c) = (bb', b'c + c') と定義することで表現できる。

  • (区間最大値更新・区間取得) 競プロ典型 90 問 029 - Long Bricks(★5)
    更新操作は最大値を区間取得 -> 区間代入操作の組み合わせで実現できる。S=(N,max,0)S = (\mathbb{N}, \max, 0) で、XX はセグメント木の具体例で考えたものと同じ。
    1. インデックスの組 (l,r)(l, r) と値 b,cb, c が与えられたとき、区間 [l,r)[l, r) に対応する配列を次のように更新する。i[l,r)i \in [l, r) に対して ai=maxi=lr1ai+1a_i = \max_{i=l}^{r-1} a_i + 1 と更新する。
    2. インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の最大値 maxi=lr1ai\max_{i=l}^{r-1} a_i を計算する。
  • (区間更新・区間転倒数取得) AtCoder Library Practice Contest L - Lazy Segment Tree 0と1からなる数列 AA に対し、
    1. インデックスの組 (l,r)(l, r) と値 b,cb, c が与えられたとき、区間 [l,r)[l, r) に対応する配列を次のように更新する。i[l,r)i \in [l, r) に対して ai=1aia_i = 1 - a_i と更新する。
    2. インデックスの組 (l,r)(l, r) が与えられたとき、区間 [l,r)[l, r) に対応する配列の要素の転倒数を計算する。
  • (並び替え) AtCoder Beginner Contest 237 G - Range Sort Query

参考