2019年4月18日木曜日

JOI2016 春合宿 - マトリョーシカ人形

JOI2016 春合宿 - マトリョーシカ人形

以下、入れ子になったマトリョーシカ人形のまとまりをパスと呼ぶ。ひとまずクエリのことは忘れて、直径と高さを限定しない場合のパスを数える。実際にパスを作りながら、配列dpdpに各パスのもっとも外側の直径を保持する。その際、dpdpは常に降順になるようにする。dpdpの長さはNNあれば十分で、00に初期化しておく。

dpdpの更新は貪欲でよい。つまり、マトリョーシカ人形をあらかじめ高さの昇順(等しい場合は直径の降順)にソートしておけば、各マトリョーシカ人形(d,h)(d, h)dpdpの中で外側の直径がdd未満かつ最大のものを選んで追加すれば良いはずである。1 ステップごとに追加場所を二分探索すればいいのでO(NlogN){\mathcal O}(N \log N)で構築できる。最終的にパスが何個できているかは、dpdpの二分探索で直径11以上のパスを数えればわかる。

さて、実際にはクエリごとに直径dd'以上、高さhh'以下に限った場合のパスを数えなければならない。上の構築をよく見ると、直径dd'未満のマトリョーシカ人形を無いものとしても各マトリョーシカ人形を追加する場所は変わらない。したがって、制限を無視して構築したdpdpについて、二分探索で(直径11以上の代わりに)直径dd'以上のパスを数えれば良い。また、高さhh'以下に限るという条件については、オフラインクエリなのでクエリを高さの昇順でソートしておいて、dpdpを必要な高さまで構築しながら答えていけば良い。O((N+Q)logN){\mathcal O}((N+Q)\log N)

考察は順調に進んだのだが、最後のステップになかなか気づけなかった。クエリを並べ替えるのは知識としてはあったけれど、実際にそれが必要な問題を解いたのは初めてだと思う。

あと、なぜかdpdpにBITを使って解いてしまった。翌日見直していてようやく、BITの機能を使っていないことに気づいた。

ところで、オンラインクエリだったらどうするんだろう。よく知らないけど永続配列とか使うしかないのかな。


  1. 解いている時は明らかだと思ったけど非自明だった。あとで考えるかも。Dilworthの定理? ↩︎