洛谷 P6715 [CCO 2018] Fun Palace (神秘DP)

模拟赛的神题。想了 4h 没有拼出任意一个多项式做法。

在赛后被同学指点了一下发现状态要这么设——设 \(f_{i,j}\) 表示考虑前 \(i\) 个房间,第 \(i\) 个房间所有时间内最大有 \(j\) 个人的情况下的最大总人数。

至于为什么要这么设,我自己分析了一下感觉是因为每个房间的人数最大值其实才是会影响能不能打开其他的门的因素,而不是某一时间内有多少人。因为人可以流动,而且我们只通过最大人数就可以确定每扇门能不能开。

具体来说,分类讨论一下:

  1. 如果 \(j<a_i\),要么 \(i\)\(i+1\) 永远也连不上,此时 \(f_{i,j}+k\to f_{i+1,k}(k<b_i)\)。要么 \(i+1\) 可以把门打开来到 \(i\),此时我们 \(j\) 个人都应该是从 \(i+1\) 过来的,否则的话就与我们最大人数为 \(j\) 矛盾了(因为 \(i+1\) 能不能到 \(i\) 和当前 \(j\) 无关),此时 \(f_{i,j}+b_i\to f_{i+1,j+b_i}\)

  2. 如果 \(a_i \le j < a_i+b_i\),代表 \(i\)(一部分) 人能到 \(i+1\),具体来说是 \(j - a_i\) 人能到 \(i+1\),因为还要留 \(a_i\) 个人开门。此时 \(i+1\) 的房间的所有人都应该是从 \(i\) 来的,不然 \(i+1\) 的人就能到 \(i\),最大人数就不是 \(j\) 了。所以此时 \(f_{i,j} \to f_{i+1,j-a_i}\),注意这一过程没有产生任何新人。

  3. 如果 \(j\ge a_i+b_i\),则我们可以这么想:先用 \(a_i\) 个人开门,至少 \(b_i\) 个人过去 \(i+1\),再用这 \(b_i\) 个人开门,剩下的人走到 \(i+1\)。这样相当于是,所有的 \(j\) 个人都可以自由在 \(i\)\(i+1\) 之间来回走,所以是 \(f_{i,j}\to f_{i+1,j}\)。注意这一过程也没有任何的新人。

于是这个转移就做完了。这个状态设计和分讨的转移感觉都特别高手,同时也很神秘。首先这样转移答案一定不会更多是肯定的,因为我们可以从转移的路径还原回一个合法的方案。但是为什么这样一定能覆盖到最优解呢?一个感性的理解是,我们的状态其实能覆盖到所有的合法方案,所以一定不会漏掉解法。同时由于我们的转移也能与方案的决策一一对应,所以解法一定是最优的。

最后的时间复杂度 \(O(nV)\)

const int MAXN = 1e3 + 5, MAXM = 2e4 + 5;
int n, m, a[MAXN], b[MAXN], f[MAXN][MAXM], tmp[MAXN];

void chkmx(int &x, int y) {
	x = max(x, y);
}

void work() {
	cin >> n >> m;
	int M = m;
	for (int i = 1; i < n; ++i) {
		cin >> a[i] >> b[i];
		M = max(a[i] + b[i], M);
	}
	for (int j = 1; j < m; ++j)
		f[1][j] = j;
	for (int i = 1; i < n; ++i) {
		for (int j = 0; j <= M; ++j) {
			if (i != 1 && j < b[i - 1]) {
				chkmx(f[i][j], tmp[i - 1] + j);
			}
			if (j < a[i]) {
				if (j + b[i] <= M) chkmx(f[i + 1][j + b[i]], f[i][j] + b[i]);
				chkmx(tmp[i], f[i][j]);
			} else if (j < a[i] + b[i]) {
				chkmx(f[i + 1][j - a[i]], f[i][j]);
			} else {
				chkmx(f[i + 1][j], f[i][j]);
			}
		}
	}
	for (int j = 1; j < b[n - 1]; ++j) {
		chkmx(f[n][j], tmp[n - 1] + j);
	}
	int ans = *max_element(f[n] + 1, f[n] + 1 + M);
	cout << ans << endl;
}
posted @ 2025-10-17 07:46  小蛐蛐awa  阅读(9)  评论(0)    收藏  举报