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; }