ICPC模擬国内2017D : ゲームバランス

http://judge.u-aizu.ac.jp/onlinejudge/description.jsp?id=2826&lang=jp

問題

 N種類のモンスター s_iが昇順に与えられる。
主人公はレベル1からスタートし、自分のレベル+ X未満の敵を倒すことが出来る。
敵を倒すと max(1,\ X - |Level - s_i|)だけレベルが上昇する。
このゲームでは、 N番目の敵を倒すとゲームクリアとなる。
主人公が出来るだけ少ない戦闘回数でクリアするようにプレイしたとき、戦闘回数が M回以上になるようにパラメータ Xを求めよ。

解法

Xとクリアに必要な戦闘回数を適当にグラフにしてみると単調なことがわかります。これは、 Xが増えたからと言って戦闘回数が増える訳ではないことから明らかです。
単調性を確認できたので、 Xを2分探索することを考えます。
以下、判定部分に関して考えていきます。
効率の良いモンスターの狩り方は、自分のレベルに近いものを狩るのが良いです。このようなモンスターを高速に求めるには、lower_boundして、得られたインデックスとその次のインデックスを見れば良いです。
レベルごとにインデックスを求めておくとより高速ですが、制約が8秒と長いので今回はサボりました。
また、初手で積む場合 s_0 > 1 + Xは、1匹目のモンスターさえも狩れないときです。このときはNGを返せば良いです。

以上、考察でした。案外簡単目ですが、実装で沼ることが多いので注意です。(1敗)

実装

2分探索です。通るときは通りますが、バグるときはバグります。
という訳で判定関数の判定結果と、範囲の動き方をしっかりと確認しておきます。(コード書いてるとだんだんわからなくなってくるので)
縦軸が、戦闘回数、midが現在の Xです。

戦闘回数がm回以上の時です。最大にしたいので左側をmidに動かして、より大きなXについて調べます。

f:id:kadomachi_noiri:20201021002800p:plain

戦闘回数がm回未満の時です。これ以上Xを大きくしても仕方がないので右側をmidに動かして、より小さなXについて調べます。

f:id:kadomachi_noiri:20201021003151p:plain

今回、クリア不可能なときは戦闘回数をINFとして、より大きなXを調べています。(クリア出来ないときはXが小さすぎるので)
また、最後に求めたXがvalidかどうかの確認もしています。これは、求めたが下限や上限になってしまい条件を満たさない時を排除するためです。

コード

提出です。
AIZU ONLINE JUDGE: Code Review

vector<int> s;

int n, m;

const int INF = 1e7;


int check(int X) {
    int count = 0;
    int L = 1;

    //初手詰み
    if (s[0] >= 1 + X) {
        return INF;
    }

    while (L + X <= s[n - 1]) {
        //敵1と敵2のインデックス
        int e1 = lower_bound(s.begin(), s.end(), L) - s.begin();
        int e2 = e1 - 1;

        //out of indexでない かつ 倒せるならdiffを更新
        int diff = 1;
        if (e1 >= 0 && e1 <= n - 1 && s[e1] < L + X) {
            diff = max(diff, X - abs(L - s[e1]));
        }
        if (e2 >= 0 && e2 <= n - 1 && s[e2] < L + X) {
            diff = max(diff, X - abs(L - s[e2]));
        }
        count++;
        L += diff;
    }

    //ボスへのラストヒットを忘れない
    count++;
    return count;
}


int binary_search() {
    int left = 0;
    int right = INF + 1;
    while (right - left > 1) {
        int mid = (left + right) / 2;

        if (check(mid) >= m) {
            left = mid;
        }
        else {
            right = mid;
        }
    }

    return left;
}


void solve() {
    s.assign(n, 0);
    for (int i = 0; i < n; i++) {
        cin >> s[i];
    }

    //クリア不可能でないならok
    int ans = binary_search();
    if (check(ans) != INF) {
        cout << ans << endl;
    }
    else {
        cout << -1 << endl;
    }
}


int main() {
    while (1) {
        cin >> n >> m;
        if (n == 0 && m == 0) break;
        solve();
    }
}