2025.03.19 CW 模拟赛 C. 软件工程

C. 软件工程

不太好想.

思路

先来考虑一下一个集合的贡献如何算. 显然, 它的贡献为 \(\max(0, r_{\min} - l_{\max})\).

这样, 我们将线段按右端点升序排序就可以轻松求出 \(r_{\min}\), 也就是每次加入集合的第一个元素.

假设我们已经钦定了 \(k\) 个集合, 那么剩下的 \(n - k\) 个线段我们一定会丢在任一集合内, 我们的目的就是求出最大的贡献.

由于集合的 \(l_{\max}\) 是不确定的, 可能由于加入线段而改变, 所以我们需要最小化代价. 设当前枚举到了第 \(i\) 条线段, \(\max l\) 表示前 \(i - 1\) 个线段左端点的最大值, 那么我们的代价最小就是 \(l_i - \max l\), 同时, 我们并不需要关注此时 \(\max l\) 对应的线段在哪一个集合里面, 只需要知道它在一个集合里面就行了, 因为我们算的是总贡献, 个人感觉十分巧妙.

还有一种情况, 就是当前存在一个交集为空的集合, 那么后面的线段可以全部丢进去, 这样一定不劣 \((\)因为如果加入其它集合, 我们的 \(l_\max\) 只有可能更大\()\)​.

动态规划

那么就可以考虑设计 DP 方程了. 令 \(f_{i, j, 0/1}\) 表示当前枚举到第 \(i\) 条线段, 分出了 \(j\) 个集合, 当前是否有一个交集为空的集合的最大贡献.

对于当前没有交集为空的集合, 不难列出转移方程

\[f_{i, j, 0} = \begin{cases} f_{i - 1, j - 1, 0} + (r_i - l_i) \\ f_{i - 1, j, 0} + (l_i - \max l) \end{cases} \]

分别表示我们新创建一个集合和加入前面的集合的贡献.

接下来考虑存在一个交集为空的集合

\[f_{i, j, 1} = \begin{cases} f_{i - 1, j - 1, 1} + (r_i - l_i) \\ f_{i - 1, j, 1} \\ f_{i - 1, j - 1, 0} \end{cases} \]

第一个式子不难理解. 第二个式子因为当前存在交集为空的集合, 我们选择将这条线段加入进该集合, 不产生代价. 第三个式子代表我们钦定当前新创建的集合是交集为空的集合. 可能出现将后面所有线段都加入这个集合都不能使得其交集为空的情况, 不过因为我们求的是最大值, 这种非法的情况一定是不优的.

最后在实现上将 \(f\) 数组初始赋为负无穷, \(f_{0, 0, 0} = 0\) 即可. 时间复杂度 \(\mathcal{O}(nk)\).

for (int i = 1, mx = 0; i <= n; ++i) {
	int _ = min(k, i);
	for (int j = 1; j <= _; ++j) {
		f[i][j][0] = max(f[i][j][0], f[i - 1][j - 1][0] + a[i].r - a[i].l);
		f[i][j][1] = max({f[i][j][1], f[i - 1][j - 1][0], f[i - 1][j - 1][1] + a[i].r - a[i].l});
	}
	for (int j = 1; j <= _; ++j) {
		f[i][j][0] = max(f[i][j][0], f[i - 1][j][0] - max(0, a[i].l - mx));
		f[i][j][1] = max(f[i][j][1], f[i - 1][j][1]);
	}
	mx = max(mx, a[i].l);
}
printf("%lld", max(f[n][k][0], f[n][k][1]));
posted @ 2025-03-19 19:40  Steven1013  阅读(38)  评论(0)    收藏  举报