Joeさんの実装で、pushdown
中でpushup
が必要な理由がわからなくてずっと悩んでいた。この実装ではpushdown
→子に降りる→…→戻ってくる→pushup
というふうに常にセットで呼んでいるし、acc
の値は子には影響しないのだから、pushup
でacc
を更新するのは戻ってきたタイミングだけで十分に見える……のだが、自分の実装(Common Lisp)でpushdown
中のpushup
を消すと正しく動かない。 しかし、ようやく解決した。結論から言えば、
- Joeさんの実装では(たぶん)
pushdown
中のpushup
はいらない。とりあえずAOJ DSL_2_HとAOJ 1508では確認済み。 - 手元で正しく動かなかったのは、自分の実装がバグっていたから。
結果は平凡だが、遅延更新Treap(merge/splitベース)の実装のポイントがわかったのが収穫だった。
- すべての操作は、
acc
、lazy
、value
等が全体として整合的になっているTreapを返さなくてはならない。これは当然。 - それに加えて
merge
/split
の返すTreapの根(top)は評価済みでなくてはならない。 つまり、根に限ってはlazy
やrev
が解消されてacc
が正しい値になっている必要がある。merge
/split
以外の操作についても、常にこれを守ったほうが余計なことに気を配らずに済むと思う。 - また、
merge
/split
自身は根が未評価のTreapを正しく扱えなければならない。
という感じか。
上の原則が守られていれば、update
中のt2->acc = Modifier::op(t2->acc, x, cnt(t2));
はいらないはず。(merge
はlazy
が0でない根を適切に処理するので。これも確認済み。)
そのうち報告するかもしれない。でも別にバグではないので……
とりあえず、自分の実装はこれ。区間更新区間取得と1点更新区間取得があるのだが、本当は1つにまとめられるはずだ。抽象化はもう少し使ってみてからにしたい。