mayoko’s diary

プロコンとかいろいろ。

SRM 658 div1 med: Mutalisk

考えていた方針は途中までは間違ってなかったらしい。

解法

基本的な方針は二分探索。x回でSCVsを全滅させることができるかどうかを判定し,全滅できる数のうち最小のものを求めれば良い。

難しいのはその判定法。まず条件を書き連ねると,

・i個目のSCVはx回より多い回数攻撃してはならない
・9,3,1のダメージを与える回数はそれぞれx回より多くなってはならない
・それぞれのSCVに対して,そのヒットポイント以上のダメージを与えなければならない

これが条件になる。これをどう判定するかというと,dpを使う。

dp[cur][n9][n3] := (9ダメージ与える攻撃をn9回残していて3ダメージ与える攻撃をn3回残していて,cur未満のSCVを全滅させた時,1ダメージ与える攻撃の残り回数の最大値)
として,上の条件を満たすように状態を遷移させていく。

以下ソースコード

vector<int> hp;
int dp[22][140][140];
int n;

bool ok(int limit) {
    memset(dp, -1, sizeof(dp));
    dp[0][limit][limit] = limit;
    for (int i = 0; i < n; i++) {
        for (int n9 = 0; n9 <= limit; n9++) {
            for (int n3 = 0; n3 <= limit; n3++) {
                if (dp[i][n9][n3] == -1) continue;
                int n1 = dp[i][n9][n3];
                for (int nn9 = 0; nn9 <= n9; nn9++) {
                    if ((nn9-1)*9 >= hp[i]) break;
                    for (int nn3 = 0; nn3 <= n3; nn3++) {
                        if (nn3 > 0 && nn9*9+(nn3-1)*3 >= hp[i]) break;

                        int nn1 = max(0, hp[i] - (nn9*9+nn3*3));
                        if (nn1+nn3+nn9 > limit) continue;
                        if (nn1 > n1) continue;

                        dp[i+1][n9-nn9][n3-nn3] = max(dp[i+1][n9-nn9][n3-nn3], n1-nn1);
                        if (i+1 == n) return true;
                    }
                }
            }
        }
    }
    return false;
}

class Mutalisk {
public:
    int minimalAttacks(vector <int> X) {
        hp = X;
        n = hp.size();
        int low = 0, high = 140;
        while (high - low > 1) {
            int med = (high+low) / 2;
            if (!ok(med)) low = med;
            else high = med;
        }
        return high;
    }
};

すごく良い問題だと思います。