Loading

【题解】「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;
}
posted @ 2021-11-16 11:34  7KByte  阅读(122)  评论(0编辑  收藏  举报