AOJ 2156 Magic Slayer

問題概要

N 体のモンスターがいる.それぞれの体力は HP_i である.
自分が使える単体魔法と全体魔法がM個与えられる.それぞれの魔法には消費MPとダメージ量が決まっている.
この時,敵を全滅させるのに必要な最小のMPを求めよ.

・制約
1 <= N <= 100
1 <= HP_i <= 10^5
1 <= M <= 100
0 <= MP_j <= 99
0 <= damage_j <= 999999

解法

典型DP.この問題は実質ドラクエの「まんたん」と同じ.
まず,単体魔法しか無い場合は,それぞれの敵に対して個別にDPすればいいというのはすぐ分かる.
最終的に単体魔法だけ考えればいいことにできれば楽.なので,先に全体魔法によってあるダメージを与えるときに必要な最小のMPを求めておく.
cost[0][i] := 単体魔法だけ用いたときに,i 以上のダメージを与えるために必要な最小のMP.
cost[1][i] := 全体魔法だけ用いたときに,ちょうど i のダメージを与えるために必要な最小のMP.
これらを前計算しておけば,「ちょうど i だけ全体魔法でダメージを与えたときに,全滅させるのに必要な最小MP」は,「全体の体力から i だけ引いた後,単体魔法だけで全滅させるのに必要な最小MP」となるので,ここまでくればあとはやるだけ.
計算量は O((N+M)HP).

全部が全体(ないし単体)魔法のケース(1WA食らった.if で対処)や,単体魔法はあるけど全てダメージ0の場合(1WA食らった.ダメージ0の魔法をあらかじめ取り除くことで対処)などのコーナーケースに注意しよう.

ソースコード

#include <iostream>
#include <vector>
#include <string>
#include <cmath>
using namespace std;
using P = pair<int, int>;

constexpr int INF = 1e9;

int main() {
    int N;
    while(cin >> N, N) {
        vector<int> hp(N);
        int max_hp = 0;
        for(int i=0; i<N; ++i) {
            cin >> hp[i];
            max_hp = max(max_hp, hp[i]);
        }
        int M;
        cin >> M;
        vector<vector<P>> s(2);
        for(int i=0; i<M; ++i) {
            string name, target;
            int mp, dam;
            cin >> name >> mp >> target >> dam;
            if(dam == 0) {
                continue;
            }
            s[target == "All"].emplace_back(mp, dam);
        }
        vector<vector<int>> cost(2, vector<int>(max_hp+1, INF));
        cost[0][0] = cost[1][0] = 0;
        for(int k=0; k<2; ++k) {
            for(int i=0; i<s[k].size(); ++i) {
                for(int j=0; j<=max_hp; ++j) {
                    int t = min(max_hp, j+s[k][i].second);
                    cost[k][t] = min(cost[k][t], cost[k][j] + s[k][i].first);
                }
            }
        }
        for(int j=max_hp; j>=1; --j) {
            cost[0][j-1] = min(cost[0][j-1], cost[0][j]);
        }
        if(s[0].size() == 0) {
            cout << cost[1][max_hp] << endl;
        } else {
            int res = INF;
            for(int i=0; i<=max_hp; ++i) {
                if(cost[1][i] == INF) {
                    continue;
                }
                int t = cost[1][i];
                for(int j=0; j<N; ++j) {
                    int rest_hp = hp[j] - i;
                    if(rest_hp <= 0 || cost[0][rest_hp] == INF) {
                        continue;
                    }
                    t += cost[0][rest_hp];
                }
                res = min(res, t);
            }
            cout << res << endl;
        }
    }
}