概要
通常のセグメント木では、管理対象の列の項数 を固定して完全二分木を構築するため、初期化に時間計算量および空間計算量 を要し、1 回のクエリあたり時間計算量 を要する。 したがって、 として利用できる値には計算機資源上の制限があり、列のインデックス (セグメント木の定義域) としてより大きな値を利用するには、オフライン クエリ (入力値がすべて予め与えられる) を前提として座標圧縮をする必要があった。
そこで本稿では、セグメント木の定義域を拡張し、例えば や のような範囲を利用できる方法について記述する。
これにより、座標圧縮をせずにセグメント木のオンライン クエリ (入力値が逐次的に与えられる) を処理することができる。
次の 2 つの方法がある:
- 方法 1 : 完全二分木のノードのうち、各更新クエリで指定されたインデックスに対応する葉と、根を結ぶパス上のノードのみ保持する。
- 方法 2 : 方法 1 によるノードのうち、子ノードの個数が 1 のノードを保持しない。
導入
まず準備として、空間 に対し、二項演算 と単位元 のペアで特徴付けられるモノイドがあるとします。 空間 の値としては実数、行列などが該当し、二項演算 としては加算、乗算、 などが該当します。 また、二項演算 の時間計算量は であると仮定しておきます。
セグメント木は通常、項数 の列に対して区間の集計値を動的に求められるデータ構造である、と説明されると思います。 厳密に書くと、次のような操作ができるデータ構造です:
を非負の整数とする。
項数 の列 およびモノイド に対し、初期値を としたうえで、以下のクエリが任意に実行される。
- (1) の値を更新する
- (2) の値を取得する
初期化に時間計算量および空間計算量 を要し、1 回のクエリあたり時間計算量 を要する。
ここで初期化に要する時間計算量および空間計算量を に改善する方法を考えていきます。
このページではまず、方法 1 について記述します。
方針
通常のセグメント木は、列の項数 を固定して、完全二分木を構築して初期化します。
下図は の例です。
方法 1 では、根は例えば を表すこととし、更新クエリ (1) が実行されたときのみ、指定されたインデックスに対応する葉ノードを追加します。
このとき、完全二分木における根と葉を結ぶパス上のノードをすべて追加します。
初期状態ではノードを持ちません。
下図は、クエリ (1) によりインデックス の値が更新されたときの例です。
クエリ (1) が実行されていないインデックス については のままです。 したがって、各ノードにおいて二項演算 により集計する際、左または右の子ノードが存在しない場合は、暗黙的に単位元 が設定されていると見なせばよいです。
以上の方針をノードベース (ノードをオブジェクトで表現する) で実装すれば、初期化に要する時間計算量および空間計算量は で済みます。
なお、配列ベースで実装することも可能で、クエリが 回実行される場合は、サイズ の配列を初期化すればよいです。
問題集およびソースコード
上記の方法を利用できる問題およびコードを示します。言語は C# です。
これらのソースコードでは、定義域を としています。
ABC 221 E では Range Sum Query (RSQ) に特化したコードを使うことができます。
加算はモノイドの中でも性質がよいため、RSQ のコードを簡略化できます。
次回は、方法 2 について記述します。
作成したサンプル
検証したバージョン
参照
- Mermaid (グラフの作図)
- セグメント木の定義域を拡張する (2)
履歴
- 2024.03.28 問題集に PAST 13 L を追加