2019年7月10日水曜日

ARC 073 F - Many Moves

ARC 073 F - Many Moves

クエリxix_iを処理した際に、動かさなかったほうのコマが位置yyにある場合の最短時間をdp[i][y]dp[i][y]とせよ。この時、コマは位置xix_iyyにある。ここからクエリxi+1x_{i+1}を処理するとき、コマをxix_ixi+1x_{i+1}と移動させ、yyのコマを動かさないなら

dp[i+1][y]=dp[i][y]+xixi+1(1)dp[i+1][y] = dp[i][y] + |x_{i} - x_{i+1}| \qquad (1)

yyxi+1x_{i+1}と移動させ、xix_{i}のコマを動かさないなら

dp[i+1][xi]=miny(dp[i][y]+yxi+1)(2)dp[i+1][x_i] = \min_y (dp[i][y] + |y - x_{i+1}|) \qquad (2)

これで計算できる形になっているので、初期化をdp[1][B]:=x1A,dp[1][A]:=x1Bdp[1][B] := |x_1 - A|, dp[1][A] := |x_1-B|、それ以外は++\inftyとしてDPすれば答えは求まる。ただ、このままでは間に合わないので遷移を工夫する必要がある。

(2)(2)の右辺は、次のように絶対値を外して書ける:

dp[i+1][xi]:=min{minyxi+1(dp[i][y]+y)xi+1miny<xi+1(dp[i][y]y)+xi+1dp[i+1][x_i] := \min \begin{cases} \min_{y \ge x_{i+1}} (dp[i][y]+y)-x_{i+1} \\ \min_{y < x_{i+1}} (dp[i][y]-y)+x_{i+1}\end{cases}

それぞれのmin\minはたぶんセグメント木等で得られる形になっているので、dpdpを1次元のセグメント木にしてO(logN){\mathcal O}(\log N)で遷移できそう。具体的には、

T:=min{minyxi+1(dp[y]+y)xi+1miny<xi+1(dp[y]y)+xi+1(3)T := \min \begin{cases} \min_{y \ge x_{i+1}} (dp[y]+y)-x_{i+1} \\ \min_{y < x_{i+1}} (dp[y]-y)+x_{i+1}\end{cases} \qquad (3)

を求めたあと、dpdp全体にxixi+1|x_i - x_{i+1}|を加算し、最後にdp[xi]:=Tdp[x_i] := Tと更新し直せばよい。

悩んだのは(3)(3)の計算をどう実現するかで、テクニックを知らなかったのでもっとも直接的な扱い方をした:

セグメント木の元を((y+,d+),(y,d))((y^+, d^+), (y^-, d^-))と4値で持ち、演算を

((y1+,d1+),(y1,d1))((y2+,d2+),(y2,d2))=((y1+,d1+)(y2+,d2+)y1++d1+,y2++d2+,(y1,d1)(y2,d2)y1d1,y2d2)((y^+_1, d^+_1), (y^-_1, d^-_1)) \oplus ((y^+_2, d^+_2), (y^-_2, d^-_2)) \\ = ((y^+_1, d^+_1)と(y^+_2, d^+_2)のうちy^+_1+d^+_1, y^+_2+d^+_2の小さいほう,\\ \qquad (y^-_1, d^-_1)と(y^-_2, d^-_2)のうちy^-_1-d^-_1, y^-_2-d^-_2の小さいほう)

で定める。値Δ\Deltaの区間加算は
((y+,d+),(y,d))((y++Δ,d+),(y+Δ,d))((y^+, d^+), (y^-, d^-)) \mapsto ((y^+ +\Delta, d^+), (y^- + \Delta, d^-))

でよい。

これでACできたが、素朴すぎて定数倍がかなり重かった。1

skyさんの解説にもっとスマートな方法が載っていた。最初からdpl[y]:=dp[y]+Ny,dpr[y]:=dp[y]+ydpl [y] := dp[y] + N - y, dpr[y] := dp[y] +yみたいに補正したものを用意すればよいらしい。なるほど……


  1. そもそもコンパイル時間だけで700ms消費している。 ↩︎