题目大意
给定 个区间,编号 。这些区间有各自的价值与费用。其中,区间 的左右端点坐标分别为 、,长度为 ,价值为 ,费用为 。
现在,你要从这些区间中选出一些区间,使得它们的总长恰好为 ,并且两两之间没有重叠(重叠部分长度为 )。
求最大总价值。
对于 的测试数据,保证 ,,,,,。
思路
这道题的关键点在于数据范围。由于选择的区间要求不重叠且数据范围的 在 之内,那么如果有解必然覆盖整个数轴,也就是区间首尾相连。 最后对于有解的问题就转换成了删除不符合条件的问题。
之后能看出肯定是背包问题。我们去考虑怎么背包。首先,dp 要做的事就是寻找两个不同东西之间的联系。本题唯一的东西就是区间了,那我们想一想怎么去考虑这一点。
本题的关系就是 了,但是我们发现光一个 很明显是不够的,然后根据之前的结论,我们可以用现在覆盖的范围 作为另外一个维度。这是因为我们总是需要看看重不重叠的。再然后,发现本题还有一个限制 ,那么再定义一维表示不超过 的花费。
然后我们发现刚开始的 就可以省略了。之后考虑转移。
那么我们记录一下每一个数结尾的地方。然后对于现在覆盖范围 ,从每一个以 结尾的地方进行转移。设这个区间为 ,则从 转移即可。
注意边界,为了方便将除了 的其他位置都设成 即可。 是因为明显覆盖 也花费 的最大值为 ,其他的都可以从转移得来。
代码
#include <iostream>
#include <vector>
#include <cstring>
using namespace std;
typedef long long ll;
const ll MAXL = 1e3 + 5;
const ll MAXN = 1e4 + 5;
ll n, l, m, dp[MAXL][MAXL];
vector<ll> e[MAXL];
struct node {
ll l, r, v, w;
} a[MAXN];
int main() {
scanf("%lld%lld%lld", &n, &l, &m);
for (int i = 1; i <= n; ++i) {
scanf("%lld%lld%lld%lld", &a[i].l, &a[i].r, &a[i].v, &a[i].w);
e[a[i].r].push_back(i);
}
memset(dp, ~0x3f, sizeof(dp));
dp[0][0] = 0;
for (int i = 1; i <= l; ++i) {
for (int j = m; j >= 0; --j) {
for (int k = 0; k < e[i].size(); ++k) {
ll num = e[i][k];
if (j >= a[num].w) {
dp[i][j] = max(dp[i][j], dp[a[num].l][j - a[num].w] + a[num].v);
}
}
}
}
ll ans = -1;
for (int i = 0; i <= m; ++i) {
ans = max(ans, dp[l][i]);
}
printf("%lld\n", ans);
return 0;
}
总结
在之后的做题中,也要时刻关注数据范围所给的提示,不光是时间复杂度。以后做 dp 时我们定义时要做的就是从最基础的特征表示开始(如 表示第 个)持续添加必须要有区分度的维度。