2019年7月10日水曜日

ARC 073 F - Many Moves

ARC 073 F - Many Moves

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

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

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

dp[i+1][xi]=min⁡y(dp[i][y]+∣y−xi+1∣)(2)dp[i+1][x_i] = \min_y (dp[i][y] + |y - x_{i+1}|) \qquad (2)

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

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

dp[i+1][xi]:=min⁡{min⁡y≥xi+1(dp[i][y]+y)−xi+1min⁡y<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(log⁡N){\mathcal O}(\log N)で遷移できそう。具体的には、

T:=min⁡{min⁡y≥xi+1(dp[y]+y)−xi+1min⁡y<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全体に∣xi−xi+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−)のうちy1−−d1−,y2−−d2−の小さいほう)((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]+N−y,dpr[y]:=dp[y]+ydpl [y] := dp[y] + N - y, dpr[y] := dp[y] +yみたいに補正したものを用意すればよいらしい。なるほど……


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