2019年3月15日金曜日

Implicit Treapの実装

Implicit Treapの実装

Joeさんの実装で、pushdown中でpushupが必要な理由がわからなくてずっと悩んでいた。この実装ではpushdown→子に降りる→…→戻ってくる→pushupというふうに常にセットで呼んでいるし、accの値は子には影響しないのだから、pushupaccを更新するのは戻ってきたタイミングだけで十分に見える……のだが、自分の実装(Common Lisp)でpushdown中のpushupを消すと正しく動かない。 しかし、ようやく解決した。結論から言えば、

  1. Joeさんの実装では(たぶん)pushdown中のpushupはいらない。とりあえずAOJ DSL_2_HAOJ 1508では確認済み。
  2. 手元で正しく動かなかったのは、自分の実装がバグっていたから。

結果は平凡だが、遅延更新Treap(merge/splitベース)の実装のポイントがわかったのが収穫だった。

  • すべての操作は、acclazyvalue等が全体として整合的になっているTreapを返さなくてはならない。これは当然。
  • それに加えて merge/splitの返すTreapの根(top)は評価済みでなくてはならない。 つまり、根に限ってはlazyrevが解消されてaccが正しい値になっている必要がある。merge/split以外の操作についても、常にこれを守ったほうが余計なことに気を配らずに済むと思う。
  • また、merge/split自身は根が未評価のTreapを正しく扱えなければならない。

という感じか。

上の原則が守られていれば、update中のt2->acc = Modifier::op(t2->acc, x, cnt(t2));はいらないはず。(mergelazyが0でない根を適切に処理するので。これも確認済み。)

そのうち報告するかもしれない。でも別にバグではないので……

とりあえず、自分の実装はこれ。区間更新区間取得と1点更新区間取得があるのだが、本当は1つにまとめられるはずだ。抽象化はもう少し使ってみてからにしたい。