hama-du-competitive.hatenablog.com
前回の記事の最後で紹介した練習問題の解答編。問題を整理して再掲する。
長さ の数列 が与えられる。クエリに 個答えよ。
全ての問題において、制約は以下の通り。
- 数列の長さ:
- 要素の値:
- クエリの数:
問題1
i
番目の値をv
に変更する。l
番目からr
番目の値の中で、最小値とそのインデックスを求める。複数ある場合は、インデックスの合計 を出力。
問題2
i
番目の値をv
に変更する。l
番目からr
番目の値の中で、 最も左端で*1v
以下になるインデックス を求める。存在しない場合はその旨を報告。
問題3
l
番目からr
番目の値をv
に変更する。l
番目からr
番目の値の中で、最小値とそのインデックスを求める。複数ある場合は、最も左端の*2インデックスを求めること。
解答1
通常の「区間の最小値(min
とおく)」を持つRMQセグメントツリーに、「区間内の最小インデックスの和(sumIndex
とおく)」を持たせればよい。
区間をマージするときは、子の min
が等しいときは sumIndex
を合計、そうでないときは min
が小さい方に合わせる。ただし、sumIndex
が 32bit整数に収まらないことがあるので注意(露骨な罠)
解答例では、区間に入ってる値たちをクラスに包んで実装してみた*3*4。以下3つを準備しておくと便利だろう
- 指定の
min
、sumIndex
で生成(コンストラクタ) - ダミー値(今回の場合は
min=INF
,sumIndex=0
)を生成する関数 - 値をコピーする関数(clone)
解答2
追加の情報を持たせる必要はないが、探索を少し工夫する。区間最小値を求める時と同じノリで、場合分けをしよう。
↑2つに当てはまらない、ということは、今自分が最下段にいるか、左右の子に答えがあるかのどちらかなので、
とすれば、区間を訪れる数が に収まる。
解答3
区間更新クエリに対する常套手段として、区間更新を最も広い区間たちで一旦受け止めておいて、区間を触るときに子に伝搬するというテクニックがある。 少し高度な概念になるが、本稿では理屈については扱わず適用方法だけを述べる。*5
「最小値(min
)」「最左インデックス(minIndex
)」に加え、「遅延評価中の最小値(delayMin
)」「遅延評価中の最左インデックス(delayMinIndex
)」を持っておく。delayMin
および delayMinIndex
は「値を持っていない」ことを示す適当な値を入れておく。
「区間更新」「区間参照」どちらも、上から降りていく形で作業が進むが、このとき区間にたどり着くたびに、以下の処理を行う。(実装上例ではpushDown関数)
- 今の区間の
delayMin
およびdelayMinIndex
が空の場合、何もしない。 delayMin
およびdelayMinIndex
を 自身のmin
minIndex
に適用する。- 子がいない場合は終了。
- 値を「子に降ろし」て、
delayMin
,delayMinIndex
を空にする。ただし、delayMinIndex
を右の子に下ろすとき、値を変更する必要があるので注意。*6
また、左右の子の再帰関数から戻ってきた後は、以下の処理を行う。(実装例では pullUp関数)
- 左右の子の値
min
minIndex
*7 から値を「吸い上げ」て自分のmin
minIndex
を更新する。
解答コード
長いのでリンクのみ示す。(gist)