区間内の最大値/最小値のインデックスを求めるRMQの実装を求めています。
— tookunn (@tookunn_1213) October 6, 2016
この問題を以下のように定式化した。効率よく解けるだろうか?
長さ の数列 が与えられる。以下のクエリに 個答えよ。
- クエリ1 :
i
番目の値をv
に変更する。 - クエリ2 :
l
番目からr
番目の値の中で、最小値とそのインデックスを求める。複数ある場合は、最も左端の*1インデックスを求めること。
制約は以下の通り。
- 数列の長さ:
- 要素の値:
- クエリの数:
単に最小値だけが欲しい場合は、RMQ*2を処理するセグメントツリーを実装すれば良い。詳細については、「プログラミングコンテストチャレンジブック 」*3 もしくは Goで型を挿げ替え可能なデータ構造ライブラリを作る - Qiita *4 を参照のこと。
この通常のセグメントツリーに少し手を加えることで、クエリ1, および2を効率良く処理することができる。
それには、各ノードに対し「区間の最小値(min
とおく)」に加え、「区間中に最小値を達成した最も左端のインデックス(minIndex
とおく)」を持つようにする。
区間をマージするときは、次のようにする。
まとめて、左の区間値が右の区間値よりも等しいか小さい場合、値を左側の区間に合わせる、とすると実装が楽になるだろう。Javaによる実装は以下の通り。
public int[] merge(int leftMin, int leftIdx, int rightMin, int rightIdx) { if (leftMin <= rightMin) { return new int[]{leftMin, leftIdx}; } else { return new int[]{rightMin, rightIdx}; } }
実装例
練習問題
以下のクエリにも答えてみよう。☆は本問と同程度、★は少し難しい。
- クエリ3☆ :
l
番目からr
番目の値の中で、最小値とそのインデックスを求める。複数ある場合は、インデックスの合計 を出力。 - クエリ4☆ :
l
番目からr
番目の値の中で、 最も左端で*5v
以下になるインデックス を求める。存在しない場合はその旨を報告。 - クエリ5★ :
l
番目からr
番目の値をv
に変更する。その上で、クエリ2 に正しく答えること。