D - 道路の整備 / Road Maintenance Editorial by admin Editorial for printing
claude4.8opus-high概要
各区画の高さを増やす(減らせない)操作によって、隣接区画の高さの差の絶対値をすべて \(D\) 以下にするための最小コストを求める問題です。各区画が最終的に取るべき「最小の高さ」を求め、そこまで上げるコストを合計すれば答えになります。
考察
重要な観察:各区画には「最終的にこの高さ以上でなければならない」という下限が存在する
まず、高さは減らせないので、最終的な高さ \(A_i'\) は必ず \(A_i' \geq A_i\) を満たします。
さらに、隣接区画の差が \(D\) 以下という制約から、ある区画 \(j\) の高さが \(A_j\) であるなら、そこから距離 \(|i-j|\) だけ離れた区画 \(i\) は少なくとも
\[A_i' \geq A_j - D \cdot |i-j|\]
を満たさなければなりません(隣に移るたびに最大 \(D\) ずつしか下げられないため)。
これがすべての \(j\) について成り立つ必要があるので、区画 \(i\) の高さの下限は
\[h_i = \max_{1 \leq j \leq N} \bigl(A_j - D \cdot |i-j|\bigr)\]
となります(\(j=i\) の項が \(A_i\) なので、自然に \(h_i \geq A_i\) も満たされます)。
この下限 \(h_i\) 自体が実現可能な解になっている
実は \(h_i\) をそのまま最終高さに採用すると、隣接する区画同士で \(|h_i - h_{i+1}| \leq D\) が成り立ちます(各 \(A_j - D|i-j|\) という項が隣接間で高々 \(D\) しか変化しないため、その最大値も同様)。
つまり \(h_i\) は「すべての制約を満たす最小の高さ」を各区画について同時に達成している 各座標ごとの最小解 です。任意の実現可能解は \(A_i' \geq h_i\) を満たすので、コスト \(C_i > 0\) が正であることから、すべての区画を \(h_i\) ちょうどに設定するのが最小コストになります。
よって答えは
\[\sum_{i=1}^{N} C_i \cdot (h_i - A_i)\]
素朴なアプローチの問題点
\(h_i\) を定義通り「各 \(i\) についてすべての \(j\) を調べる」と \(O(N^2)\) かかり、\(N \leq 10^6\) では間に合いません。工夫が必要です。
アルゴリズム
\(h_i = \max_j (A_j - D|i-j|)\) を、絶対値を左右に分けて 前方・後方の 2 回の走査 で計算します。
左側からの寄与(\(j \leq i\)):\(A_j - D(i-j)\) の最大値を $\(L_i = \max(A_i,\ L_{i-1} - D)\)\( という漸化式で左から順に計算できます。1 つ右へ進むと過去の候補はすべて \)D$ ずつ小さくなるためです。
右側からの寄与(\(j \geq i\)):同様に $\(R_i = \max(A_i,\ R_{i+1} - D)\)$ を右から計算します。
最終的に \(h_i = \max(L_i, R_i)\) とすれば、\(O(N)\) で全区画の下限が求まります。
コードでは前方走査で h[i] に \(L_i\) を入れ、後方走査で \(R_i\) を計算しながら h[i] と大きい方を取る、という形で 2 パスを実現しています。
具体例:\(A = [5, 0, 0, 3]\), \(D = 2\) の場合
- 前方走査:\(L = [5, 3, 1, 3]\)
- 後方走査:\(R = [5, 1, 3, 3]\)
- \(h = \max(L, R) = [5, 3, 3, 3]\)
たとえば区画 2 は、区画 1 の高さ \(5\) から \(D=2\) 下げても \(3\) までしか下げられないので \(h_2 = 3\) となっています。増やすコストは \(\sum C_i(h_i - A_i)\) で計算されます。
計算量
- 時間計算量: \(O(N)\)
- 空間計算量: \(O(N)\)
実装のポイント
\(A_i, C_i\) が大きく、\(N\) も大きいため、答えは 64bit 整数の範囲になり得ます。Python では自動的に多倍長整数になるので問題ありませんが、C++ 等では
long longを使う必要があります。\(N = 10^6\) と大きいので、入力は
sys.stdin.buffer.read()で一括読み込みして高速化しています。前方走査の値を
hに保持しておき、後方走査でmaxを取りながら上書きすることで、余分な配列を増やさず 2 パスで完結させています。ソースコード
import sys
def main():
data = sys.stdin.buffer.read().split()
idx = 0
N = int(data[idx]); idx += 1
D = int(data[idx]); idx += 1
A = [int(x) for x in data[idx:idx+N]]; idx += N
C = [int(x) for x in data[idx:idx+N]]; idx += N
# h_i = max_j (A_j - D*|i-j|) via forward and backward passes
h = A[:]
# forward pass
prev = A[0]
for i in range(1, N):
v = prev - D
ai = A[i]
cur = ai if ai > v else v
h[i] = cur
prev = cur
# backward pass, combine with h (take max)
prev = A[N-1]
# last element: forward value already A[N-1] effectively, but ensure
if A[N-1] > h[N-1]:
h[N-1] = A[N-1]
for i in range(N-2, -1, -1):
v = prev - D
ai = A[i]
b = ai if ai > v else v
prev = b
if b > h[i]:
h[i] = b
ans = 0
for i in range(N):
ans += C[i] * (h[i] - A[i])
sys.stdout.write(str(ans) + "\n")
main()
この解説は claude4.8opus-high によって生成されました。
posted:
last update: