はまやんはまやんはまやん

hamayanhamayan's blog

水ようかん (Mizuyokan) [第17回日本情報オリンピック 予選 D]

https://atcoder.jp/contests/joi2018yo/tasks/joi2018_yo_d

解法

https://atcoder.jp/contests/joi2018yo/submissions/8137211

気付くべき性質がある
「取りうる羊羹のサイズはO(N^2)通りしか無い」
取りうるサイズを全列挙して、sort->eraseの座標圧縮の要領でダブリを排除しておこう。
これが配列v

これより、羊羹を分けた時の最小と最大に来るサイズもそれぞれO(N^2)通り
より、最小と最大を全探索できる。O(N^4)

あとは、最小と最大を達成できるかであるが、メモ化再帰で確認していこう。
「f(idx, mi, ma) := 分割した最小がmi,最大がmaとなるように[idx,N-1]の区間を分割できるか」
遷移としては[idx,?]の?の部分を総和が[最小,最大]をみたすなら遷移を試してみればいい。
 
累積和で区間総和を取ってきているが、これは特にやらなくても通る。

int N, L[55];
//---------------------------------------------------------------------------------------------------
int LL[55];
int getsm(int a, int b) { // L[a] + L[a + 1] + ... + L[b]
    int sm = LL[b];
    if (a) sm -= LL[a - 1];
    return sm;
}
//---------------------------------------------------------------------------------------------------
int memo[55];
int f(int idx, int mi, int ma) {
    if (0 <= memo[idx]) return memo[idx];
    if (idx == N) return 1;

    int lim = N;
    if (idx == 0) lim = N - 1;
    rep(i, idx, lim) {
        int sm = getsm(idx, i);
        if (mi <= sm and sm <= ma) {
            if (f(i + 1, mi, ma)) return memo[idx] = 1;
        }
    }

    return memo[idx] = 0;
}
//---------------------------------------------------------------------------------------------------
void _main() {
    cin >> N;
    rep(i, 0, N) cin >> L[i];
    LL[0] = L[0];
    rep(i, 1, N) LL[i] = LL[i - 1] + L[i];
    
    vector<int> v;
    rep(i, 0, N) rep(j, i, N) {
        int sm = getsm(i, j);
        v.push_back(sm);
    }
    sort(all(v));
    v.erase(unique(all(v)), v.end());
    
    int n = v.size();
    int ans = inf;
    rep(i, 0, n) rep(j, i, n) {
        int mi = v[i];
        int ma = v[j];
        rep(k, 0, N + 1) memo[k] = -1;
        if (f(0, mi, ma)) chmin(ans, ma - mi);
    }
    cout << ans << endl;
}