けんちょんの競プロ精進記録

競プロの精進記録や小ネタを書いていきます

AtCoder ABC 320 F - Fuel Round Trip (3D, 黄色, 550 点)

少し重たかった。でもいい問題。JOI にありそう。

問題概要

数直線上に  N-1 箇所の燃料補給所がある。 i 番目の補給所は次のようになっている。

  • 座標  X_{i} にある
  • 補給すると、現在の HP が  x であるとき、HP が  \min(x + F_{i}, H) になる (コストとして  P_{i} を支払う)

今、高橋君は座標  0 を HP が  H である状態で出発して、座標  X_{N} の地点を訪れて、座標  0 の地点へと引き返す。なお、1 進むと HP が 1 減少する。

注意したいこととして、どの補給所も高々 1 回しか使えない。つまり、行きの途中で使用した補給所は、帰りには使えない。

帰ってくるまでに一度も HP が負にならないようにするための最小コストを求めよ (不可能な場合は -1)。

制約

  •  1 \le N, H \le 300
  •  0 \lt X_{1} \lt X_{2} \lt \dots \lt X_{N} \le 10^{5}

考えたこと

最初は Greedy に解けないかと考えた。しかし、帰りのことも考えながら、行きで使う補給所を選ぶのは無理そうだった。ならば DP。

帰りの部分については、操作を逆に考えて


  • 1 進むごとに HP が 1 増えてしまう
  • HP が  H を超えてはいけない
  • 補給所では、HP が  H 未満の場合は  F_{i} だけ減らし、HP が  H である場合は  H - F_{i} 以上  H 以下の任意の値にすることができる

というように考えることにした。そうして、次の DP を組む。

dp[i][j][k] i 番目の補給所にたどり着いた段階で、行き側の HP が  j、帰り側の HP が  k となるようにする場合の最小コスト (座標 0 を 0 番目の補給所とみなす)

計算量は  O(NH^{2}) となる。

コード

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

template<class T> inline bool chmax(T& a, T b) { if (a < b) { a = b; return 1; } return 0; }
template<class T> inline bool chmin(T& a, T b) { if (a > b) { a = b; return 1; } return 0; }

const int INF = 1<<30;
int main() {
    int N, H;
    cin >> N >> H;
    vector<int> X(N), P(N-1), F(N-1);
    for (int i = 0; i < N; ++i) cin >> X[i];
    for (int i = 0; i < N-1; ++i) cin >> P[i] >> F[i];
 
    vector<vector<int>> dp(H+1, vector<int>(H+1, INF));
    for (int j = 0; j <= H; ++j) dp[H][j] = 0;
    
    int prev = 0;
    for (int i = 0; i < N; ++i) {
        vector<vector<int>> ndp(H+1, vector<int>(H+1, INF));
        
        int dif = X[i] - prev;
        
        // 補給しない
        for (int j = 0; j <= H; ++j) {
            for (int k = 0; k <= H; ++k) {
                if (j + dif <= H && k - dif >= 0) {
                    chmin(ndp[j][k], dp[j + dif][k - dif]);
                }
            }
        }
        if (i < N - 1) {
            // 行き
            for (int j = 0; j < H; ++j) {
                for (int k = 0; k <= H; ++k) {
                    if (j - F[i] >= 0 && j - F[i] + dif <= H && k - dif >= 0) {
                        chmin(ndp[j][k], dp[j - F[i] + dif][k - dif] + P[i]);
                    }
                }
            }
            for (int pj = max(0, H - F[i]); pj <= H; ++pj) {
                for (int k = 0; k <= H; ++k) {
                    if (pj + dif <= H && k - dif >= 0) {
                        chmin(ndp[H][k], dp[pj + dif][k - dif] + P[i]);
                    }
                }
            }
            
            // 帰り
            for (int j = 0; j < H; ++j) {
                for (int k = 0; k <= H; ++k) {
                    if (k + F[i] >= H) {
                        if (j + dif <= H && H - dif >= 0) {
                            chmin(ndp[j][k], dp[j + dif][H - dif] + P[i]);
                        }
                    } else {
                        if (j + dif <= H && k + F[i] - dif >= 0) {
                            chmin(ndp[j][k], dp[j + dif][k + F[i] - dif] + P[i]);
                        }
                    }
                }
            }
        }
        swap(dp, ndp);
        prev = X[i];
    }
    
    int res = INF;
    for (int v = 0; v <= H; ++v) chmin(res, dp[v][v]);
    cout << (res < INF ? res : -1) << endl;
}