AtCoder Beginner Contest 038 D : プレゼント
当初の方針(WA)
ふつうにDPしたらO(N^2)になりそう。きっとO(N log N)解法。
ソートして大きいやつから取っていけば良さそう → WA
Submission #923373 - AtCoder Beginner Contest 038 | AtCoder
3 1 9999 1 1 2 2
のケースで落ちる。
次、min(w, h)をとってソートすれば良さそう。同じやつはmax(w, h)で降順。俺天才 → WA
Submission #923380 - AtCoder Beginner Contest 038 | AtCoder
3 1 2 2 1 3 2
のケースで落ちる。
ここであきらめて解説を見る。
解説を読んで
DPについて考える。
dp[i] := iを最後に使ったとき(i番目の箱が一番外側)の最大の入れ子数
dp[i] = max{dp[j] | h_i > h_j && w_i > w_j} + 1
ここで、h について昇順でソートすると、
dp[i] = max{dp[j] | j < i && w_i > w_j} + 1
となり、条件が簡単になった。
でも、w_i > w_j な一番大きなdp[j]を高速に求めるにはどうすれば・・・?
ここで、BIT:Binary Indexed Treeを使う。
(セグメントツリーでも良いが、1 ~ w_i-1までの範囲を探索するので、
実装が簡単なBITのほうが嬉しい)
BITでは、1 ~ w_i - 1の範囲で最大の値を返すように設定する。
何の最大値かというと、求めたいdp[j](1 <= j <= w_i - 1)。
すなわち、j番目の箱を最後に使ったときの最大の入れ子数。
これで、2つ目の条件、w_i > w_jを達成ことができる。
1つ目の条件はというと、hについてソートしてあるので、
for i=1:n で更新作業をしていけば、
j < iが守られる。
hが同じ場合のときがあるが、ソートの際wについて降順にしておけばOKらしい。
これがなぜかよく分かっていない。
昇順だと、hが同じときにも箱を入れようとしてしまうから?
なのでwについて降順にソートされていると同じhの箱を使うことがないように計算できる
(自分の位置w_iより前を探索するから)。
自分はそれで実装したが、もう一つの実装方法である、
hが変わったときにhが同じ分をまとめて更新するほうが
分かりやすかった。
Submission #923446 - AtCoder Beginner Contest 038 | AtCoder
せっかくなので、BITの実装をライブラリっぽくした。
java8のBiFunctionを使っていて、計算方法を
Integer::maxとかInteger::sumとかで指定することができる
(もちろんラムダ式も使える)。
もっときれいな書き方ができるといいのだけれど。
こうやって、必要なものをライブラリ化して手元に残したほうが、
やっぱり後々有利になるのかなあ。
解説記事を書けば理解が進むかと思ったけど、
未だ完璧に理解できている感じがしない。
要復習。
図を書いたら理解進むかな。
BITにDPの値を渡しておくイメージ。
・・・うーん。