怖くない! 抽象化動的セグメント木の非再帰実装
はじめに
区間取得クエリを対数時間で処理するセグメント木は競技プログラミングで頻出のデータ構造です。巨大な配列(MLE
や実行時エラーを引き起こしてしまいますが、必要なノードだけ作ることで空間
本記事では抽象化動的セグメント木の非再帰実装の勘所を紹介します。また、効率よく実装するための工夫についても紹介します。
対象読者
本記事ではセグメント木の仕組みを理解していることを前提とします。セグメント木の解説記事は世にごまんとあるので、適宜参照してください。個人的にわかりやすいと思う記事を1つ挙げておきます。
アルゴリズム
この節ではアルゴリズムを概観します。視覚的に理解することが目的です。
一点更新
通常のセグメント木ではすべてのセグメント[1]が適切に初期化されていますが、動的セグメント木では一点更新の度に必要に応じてセグメントをつくります。各セグメントは区間と更新されたノード、対応する区間の途中結果を持ちます。図の横軸は区間、縦軸は親子関係を表しています。
初期化
動的セグメント木は単位元で初期化されています[2]。まだ更新していないのでセグメントはありません。
更新
初期化された動的セグメント木に1回目の更新を行います。これは、木の根をつくる操作に相当します。
2回目の更新を行います。新しいノードは根の区間の右側にあるので、右の子を生やします。セグメント木は二分木なので、
区間クエリ
区間クエリは少し複雑です。まずは、アルゴリズムを観察してみます。
クエリ区間がセグメントの片方の子に包まれるとき(Type 1)
根から順に探索していく際に、セグメントのもつノードがクエリ区間に含まれるならば答えに反映させる必要があります。二項演算の計算順序を保つために少し工夫が必要です。
セグメント木が二分木であることを思い出すと、R1
の左側の子孫がもつノードのインデックスは必ず、R1
のもつノードのインデックスよりも小さいです。したがって、R1
のもつノードは一番右から合成することになります。他のノードも同様です。
区間が左右の子にまたがるとき(Type 2)
セグメントの区間とクエリ区間が完全に一致する場合は途中結果にスタックにためていた値を合成します。これが返り値です。そうでない場合、セグメントの左の子孫、自身がもつノード、右の子孫の順に値を合成します。
P
の左の子孫たちについて考えます。クエリ区間の左端はL1
の左側(外側)にあります。したがって、右側(内側)の子であるL2
の途中結果が答えに含まれます。L1
のもつノードが区間に含まれるなら、左から合成すればよいです。
次に外側にあるL3
を検討します。区間の左端が右側(内側)にあるので左側(外側)の子は不要です。L4
の子孫の情報は明記されていないため不明ですが、L3
のもつノードをいちばん左側(外側)から合成することは確定します。よって、L3
をスタックに追加し、L4
の子孫を調べていきます。
左側の子孫を調べ終わったら、P
の持つノードを必要に応じて右から合成します。右側の子孫も左側の子孫と同じように処理します。最後に、スタックに遅延していたノードの値を合成します。
実装上の注意
スタックの再利用
アルゴリズムからわかるように、スタック長さは一点更新でも区間クエリでも
アリーナ木
ノード[3]を個別にヒープに置くのではなくVec
に配置するような木をアリーナ木と言います。アリーナ木はキャッシュ効率が良く、所有権の管理も楽です。
セグメントをアリーナ木に格納する際には、左右の子へのポインターをOption<NonZeroUsize>
とするとよいです。Option
型は通常usize
1つ分の追加メモリを必要としますが、NonZeroUsize
では0
をNone
と扱う最適化が施されているため、追加メモリは必要ありません。アリーナ木の0要素目は根なので子のインデックスは常に正です。
区間クエリにおける左右の子の区別
左右の子の区別にenum
を利用するとOption
と同様に追加のメモリを必要とします。アリーナ木の実態はVec
ですが、実はVec
の容量の上限はisize::MAX
です。たとえば、Vec::with_capacity()
でisize::MAX
より大きな容量を与えるとpanic
します。したがって、usizeの最上位ビットをフラグとして利用できます。
実装例
セグメント木とその仲間たちを実装したseg_libをcrates.io
で公開しています。動的セグメント木の実装例はこちらをご覧ください。
おわりに
本記事では抽象化動的セグメント木の非再帰実装について紹介しました。一見面倒で敬遠しがちな話題ですが、図を描いてうまく整理することができました。この喜びを共有できると嬉しいです。
本記事に誤りを発見した場合、コメントにてお知らせください。また、あなたの知見を共有して頂けると嬉しいです。
Discussion