题解:CF1579G Minimal Coverage
似嫩
2021-10-04 11:42:23
## 题意简析
有多组数据
- 给你 $n$ 条线段,告诉你每条线段的**长度**。
- 你需要把它们放在一条**无限长的数轴**上。
- 放置需满足**当前线段的起点**是**前一个线段的终点**,特别的第一个线段的起点为 $0$。
也就是说,若前一个线段的终点是 $x$,当前长度为 $d$, 那么这个线段必须放在 $[x-d,x]$ 终点变为 $x-d$,或 $[x, x + d]$ 终点变为 $x + d$。
- 请问放置完后所有线段的最小覆盖长度是多少?
- $1\le t\le 10^4, 1\le n\le 10^4$
## 分析
~~好耶,居然是DP题~~
我们把覆盖看成跳跃,一条条跳进行决策显然不现实~~贪心写挂了~~。通过观察可以证明,最后的答案肯定不超过 $2\times l_{\max}$。因为所有的线段都可以在里面左右跳,而不出去(也可参见下面的解释)。
由于数轴是无限长的,从哪里开始没有影响。我们可以规定所有线段在 $[-l_{\max},l_{\max}]$ 之间,如果当前上一个结束位置大于 $0$,就向左跳,否则,就向右跳,这样就不会跳出边界。
解决了如何操作的问题,接下来首先是dp的状态:$dp_{i,l}$ 用来表示当前 $i$ 的结束点到最左边的距离是 $l$ 时,到最右边的距离最小值。于是当前覆盖的长度即为 $l+dp_{i,l}$。最后的答案即为 $\min(l+dp_{n,l})(0\le l\le 2\times l_{\max})$
然后是转移:
- 预处理:$dp_{i,j}=inf,dp_{0,0}=0$ 如果等于 $inf$,说明无法跳到,直接跳出。
- 对于每个线段考虑向左放还是向右放:
- 其中 $0\le l\le 2\times l_{\max},0\le i< n$
- 对于 $a_{i+1}$, 如果考虑向左放,那么到左边界的距离变为 $\max(l-a_{i+1},0)$(如果没有超过范围,就减一下,否则它就是现在的最左边),到右边界的距离为 $dp_{i,l}+a_{i+1}$(因为向左跳了那么多)。所以有
$$dp_{i+1,\max(0,l-a_{i+1})} = min(dp_{i+1,\max(0,l-a_{i+1})},dp_{i,l}+a_{i+1})$$
- 如果考虑向右放,那么到右边界的距离变为 $\max(dp_{i,l}-a_{i+1},0)$,到左边界的距离为 $l+a_{i+1}$,所以有
$$dp_{i+1,l+a_{i+1}}=min(dp_{i+1,l+a_{i+1}},\max(dp_{i,l}-a_{i+1},0))$$
然后我们就愉快地解决了, 复杂度 $O(n\times l_{\max})$
## 总结
想对状态还是。。嗯不难想的
~~总之也是道神仙题~~
```cpp
const int INF = 1000000000;
int main() {
int T = read();
while (T --) {
int n = read();
int maxx = 0;
for (int i = 0; i < n; i++) {
a[i] = read();
maxx = max(maxx, a[i]);
}
vector<vector<int>> dp(n + 1, vector<int>(2 * maxx + 1, INF));
dp[0][0] = 0;
for (int i = 0; i < n; i++) {
for (int left = 0; left <= 2 * maxx; left++) {
if (dp[i][left] == INF)
continue;
dp[i + 1][max(left - a[i], 0)] = min(dp[i + 1][max(left - a[i], 0)], dp[i][left] + a[i]);
if (left + a[i] < dp[i + 1].size()) {
dp[i + 1][left + a[i]] = min(dp[i + 1][left + a[i]], max(dp[i][left] - a[i], 0));
}
}
}
int ans = 2 * maxx + 1;
for (int left = 0; left <= 2 * maxx; left++)
ans = min(ans, left + dp[n][left]);
write(ans), puts("");
}
}
```