移動中に所持金が足りなくなったとき、現在いるマスで補充する代わりに、通過したうち Pi,j が最大のマスで、 「そこにいた時に、先を見越して所持金を補充しておいた」ことにしてよい。
これにより、各マスでは「所持金が Ri,j 以上になるまで補充できたら、すぐ右に移動する」(下も同様)としてよい。
行動回数と所持金の2つの値を管理しなければならない。行動回数は少ない方がよいが、所持金は多い方がよい。
2つが相反する場合、DPにはどちらを残すべきか?
答えとして求めるのは行動回数なので②は必須だが、 この先、「②では所持金が足りなくなってしまうが、①では手持ちで足りるので補充しなくて済んで、最終的に逆転する」ようなことはないか?
ないと言える。
「所持金が足りればすぐに移動する」という行動基準を考えると、 各状況の所持金を C①,C② として、0≤C①,C②<Pk,l である。
C②+Pk,l>C① なので、②で1回補充すると所持金は C② の方が大きくなる。
つまりこれ以降で、②では補充するが①ではしなくて済む(または、①の方が補充回数が少なくなる)状況が訪れるとしても、
補充回数の差はせいぜい1回で逆転まではしないし、その場合も所持金は②の方が多いので、②が完全に優位である。
(この先の通過マスで、Pk,l がより多い他のマスに更新されているかもしれないが、いずれにしろ上記の理論は変わらない)
よって、DPは②の行動回数が最小の状態(同じなら、所持金がより多い方)を残す、として問題ない。
計算量は O(N4)
PythonだとPyPyでも厳しい、Numbaでやっと通った。
ちょっと N,Q を多くしすぎじゃないですかねと思ったが、
C++ だとコンパイルオプションを指定して愚直解を通されていたみたいなので、Writerさんは大変だと思い直した。
ある1クエリ (Ai,Bi) だけに答えることを考えると、 点が上手い具合にソートされていると、二分探索で答えを求められそうである。
「上手い具合にソート」とはどんな状態かを考えると、
「各点を通る傾き Ai の直線を引くと、それらが下から順に並んでいる」と言う状態である。
クエリの直線が、どの2点(から引かれた直線)の間にあるか二分探索で調べれば、
そこから上の点は全て条件を満たすことになる。
/ / // / / // ① / ④/ この傾きでは、③④②① の順に小さい / ② // / /③ / //
Ai が変わると、並び順も変わる
\\\ \ ①\\ ④ この傾きでは、①②③④ の順に小さい \②\ \ \\③ \\\
Ai ごとにこれを求めていったらもちろんTLEだが、傾きがちょっと変わっただけでは大部分の順序は同じはず。
クエリを Ai でソートして傾きを負の無限大から正の無限大へ徐々に変化させていったとき、 各2点組の順位は「傾きが2点を結ぶ直線の傾きに等しくなった」前後に入れ替わる。
このような変化は O(N2) 箇所あり、隣り合った要素を入れ替える操作は O(1) でおこなえる。
よって、「全ての2点間の傾き」と「クエリ1で与えられる直線の傾き Ai」を、まとめてイベントソートして、 小さい方から「順位の入れ替え」または「二分探索による求解」をおこなっていけばよい。
ただし、
O((N2+Q)log(N2+Q)) で求められる。
点とクエリを別管理した方が、logの中身がばらけるので計算量は減る。ここまで N,Q が大きいと、その違いも馬鹿にならないかも。