公式

D - 道路の整備 / Road Maintenance 解説 by admin

gpt-5.5-high

概要

隣り合う区画の高さの差が \(D\) 以下になるように、各区画の高さを必要最小限だけ上げる問題です。

重要なのは、各区画の最終的な高さは「周囲の高い区画から強制される最低高さ」として一意に決まる、という点です。

考察

最終的な高さを \(B_i\) とします。

条件より、隣り合う区画の高さの差は \(D\) 以下なので、任意の区画 \(i, j\) について

\[ B_i \geq B_j - D|i-j| \]

が成り立ちます。

また、操作では高さを下げることはできないため、

\[ B_j \geq A_j \]

です。

したがって、

\[ B_i \geq A_j - D|i-j| \]

がすべての \(j\) について成り立ちます。

つまり、区画 \(i\) の最終的な高さは少なくとも

\[ \max_j (A_j - D|i-j|) \]

でなければなりません。

この値を \(H_i\) とすると、

\[ H_i = \max_j (A_j - D|i-j|) \]

です。

これは「各区画 \(j\) の元の高さ \(A_j\) が、距離 \(1\) 離れるごとに最大 \(D\) ずつしか下がれない」と考えたときに、区画 \(i\) に要求される最低高さです。

例えば、高さ \(10\) の区画があると、隣は最低でも \(10-D\)、その隣は最低でも \(10-2D\)、というように周囲に制約を与えます。

なぜこれが最適か

すべての \(C_i\) は正なので、必要以上に高さを上げると必ずコストが増えます。

また、ある区画を余分に高くしても、他の区画を低くできるわけではありません。むしろ新たな制約を生むだけです。

したがって、各区画を可能な限り低い高さ \(H_i\) にするのが最適です。

素朴な方法では難しい理由

各区画 \(i\) について、すべての区画 \(j\) を調べて

\[ \max_j (A_j - D|i-j|) \]

を計算すると、計算量は \(O(N^2)\) になります。

\(N \leq 10^6\) なので、これは到底間に合いません。

そこで、左側からの影響と右側からの影響を分けて、線形時間で計算します。

アルゴリズム

区画 \(i\) に必要な高さ \(H_i\) は、左側から来る制約と右側から来る制約の最大値です。

左側からの制約

左側、つまり \(j \leq i\) の区画から来る制約を

\[ L_i = \max_{j \leq i}(A_j - D(i-j)) \]

とします。

これは左から順に計算できます。

  • \(L_1 = A_1\)
  • \(i \geq 2\) のとき、

\[ L_i = \max(A_i, L_{i-1} - D) \]

です。

なぜなら、区画 \(i-1\) までの左側からの最大制約 \(L_{i-1}\) は、区画 \(i\) に進むと \(D\) だけ弱まるからです。

右側からの制約

同様に、右側、つまり \(j \geq i\) の区画から来る制約を

\[ R_i = \max_{j \geq i}(A_j - D(j-i)) \]

とします。

これは右から順に計算できます。

  • \(R_N = A_N\)
  • \(i \leq N-1\) のとき、

\[ R_i = \max(A_i, R_{i+1} - D) \]

です。

最終的な高さ

区画 \(i\) に必要な最終高さは、左からの制約と右からの制約の大きい方なので、

\[ H_i = \max(L_i, R_i) \]

です。

あとは、区画 \(i\)\(A_i\) から \(H_i\) まで上げるためのコスト

\[ (H_i - A_i) \times C_i \]

をすべて足し合わせれば答えになります。

具体例

例えば、

\[ A = [10, 0, 0, 7], \quad D = 3 \]

とします。

左から見ると、

\[ L = [10, 7, 4, 7] \]

です。

高さ \(10\) の区画から右へ進むたびに、最低高さは \(7, 4\) と下がっていきます。

右から見ると、

\[ R = [10, 1, 4, 7] \]

です。

したがって最終的な高さは、

\[ H = [10, 7, 4, 7] \]

になります。

隣り合う差はすべて \(3\) 以下であり、これが必要最小限の高さです。

計算量

  • 時間計算量: \(O(N)\)
  • 空間計算量: \(O(N)\)

実装のポイント

左からの制約 \(L_i\) は配列に保存しておきます。

右からの制約 \(R_i\) は、右から走査しながら現在値 cur として持てばよいので、配列として保存する必要はありません。

各区画で

h = max(L[i], cur);

として最終的な高さを求め、

ans += 1LL * (h - A[i]) * C[i];

でコストを足します。

答えは最大で非常に大きくなるため、int ではなく long long を使う必要があります。

ソースコード

#include <bits/stdc++.h>
using namespace std;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int N, D;
    cin >> N >> D;

    vector<int> A(N), C(N), L(N);
    for (int i = 0; i < N; i++) cin >> A[i];
    for (int i = 0; i < N; i++) cin >> C[i];

    int cur = 0;
    for (int i = 0; i < N; i++) {
        if (i == 0) cur = A[i];
        else cur = max(A[i], cur - D);
        L[i] = cur;
    }

    long long ans = 0;
    for (int i = N - 1; i >= 0; i--) {
        if (i == N - 1) cur = A[i];
        else cur = max(A[i], cur - D);

        int h = max(L[i], cur);
        ans += 1LL * (h - A[i]) * C[i];
    }

    cout << ans << '\n';
    return 0;
}

この解説は gpt-5.5-high によって生成されました。

投稿日時:
最終更新: