【题解】「CCO 2018 Day1」Fun Palace
值得思考的 DP 题。
我们需要观察一些性质,首先考虑只有一个管道的情况。
- 如果左边的人数 \(<a_i\),右边的人数 \(<b_i\),显然左右是互不相关的。
- 如果左右人数之和 \(\ge a_i + b_i\),那么人可以随意移动,这个管道等于被删除了。
- 如果人数之和 \(<a_i + b_i\) 但左边人数 \(\ge a_i\),那么右边最多能有 \(l+r-a_i\),人,而左边最多能有 \(l+r\) 人,这等价于将右边的人移到左边。
- 同理,如果人数之和 \(<a_i + b_i\) 但右边人数 \(\ge b_i\),那么左边最多能有 \(l+r-b_i\),人,而右边最多能有 \(l+r\) 人,这等价于将左边的人移到右边。
数据范围的值域给的很小,复杂度明显是 \(N\times \max\{a_i+b_i\}\) 的。
考虑动态规划,我们可以设计一个很巧妙的状态。\(f_{i,j}\) 表示第 \(i\) 个位置,最多有 \(j\) 个人,前 \(i\) 个位置中最多能有多少人。
直接对 \(j\) 进行讨论,以下讨论分别对应上面四种情况。
- 如果 \(j < a_i\),枚举 \(k < b_i\),\(f_{i,j}+k\to f_{i + 1, k}\)
- 如果 \(j \ge a_i + b_i\),\(f_{i,j}\to f_{i + 1, j}\)
- 如果 \(a_i\le j < a_i + b_i\),\(f_{i,j}\to f_{i + 1,j - a_i}\)
- 如果 \(a_i\le b_i+j< a_i+b_i\),\(f_{i,j}+b_i \to f_{i+1,j+b_i}\)
仔细想想会觉得这个转移非常奇怪,我们每次只在 \(1,4\) 情况下加人,可能会漏掉某些情况。但感性贪心以下,如果是情况 \(2\) ,我们可以随便移动,一定在左边某个非 \(2\) 情况下加点人。如果是情况 \(3\),右边的人可以移动到左边,所以不会优于在其左边某个非 \(3\) 位置加点。而对于整个 dp 的初始情况是 \(1\),所以我们每次只用在 \(1,4\) 情况下加点。
时间复杂度 \(\mathcal{O}(N\max\{a_i+b_i\})\)。
#define N 1005
int n, w, m, a[N], b[N], f[N][20 * N];
int main() {
//int T = read();while(T--)solve();
n = read(), m = w = read();
rp(i, n - 1)a[i] = read(), b[i] = read(), m = max(m, a[i] + b[i]);
memset(f, 0xcf, sizeof(f));
rep(i, 0, w - 1)f[1][i] = i;
rep(i, 1, n - 1){
int cur = inf_;
rep(j, 0, m - 1){
if(j >= a[i] + b[i])cmx(f[i + 1][j], f[i][j]);
else {
if(j >= a[i])cmx(f[i + 1][j - a[i]], f[i][j]);
if(j >= b[i])cmx(f[i + 1][j], f[i][j - b[i]] + b[i]);
}
if(j < a[i])cmx(cur, f[i][j]);
}
rep(j, 0, b[i] - 1)cmx(f[i + 1][j], cur + j);
}
int ans = inf_;
rep(j, 0, m - 1)cmx(ans, f[n][j]);
cout << ans << endl;
return 0;
}