题解:洛谷 P2854 Cow Roller Coaster
【题目来源】
洛谷:P2854 [USACO06DEC] Cow Roller Coaster S
【题目描述】
有 \(n\) 条线段,每条线段只能放在数轴上的一个特定位置,并且第 \(i\) 根线段有如下几个属性:\(X_i\)(该线段放在数轴上的起点),\(W_i\)(该线段长度),\(F_i\)(你若使用该线段你能获得的价值),\(C_i\)(你若使用该线段你所需要的费用)。所以对于第 \(i\) 条线段,它覆盖的区间是 \([X_i,X_i+W_i)\)。现在让你从中选出一些线段,使得这些线段能够铺满数轴上的区间 \([0,L)\) 且线段必须首尾相接(也就是不能重叠,也不能空缺),并且所花费用和不超过 \(B\),同时要使你收获的价值尽量大,请你找到这个方案。
【输入】
第一行三个整数 \(L, n, B\)。
接下来 \(n\) 行,每行四个整数 \(X_i, W_i, F_i, C_i\) 表示第 \(i\) 根线段的四个属性。
【输出】
一个整数,表示你能获得的最大价值和。若无法满足上述要求,则输出 \(-1\)。
【输入样例】
5 6 10
0 2 20 6
2 3 5 6
0 1 2 1
1 1 1 3
1 2 5 4
3 2 10 2
【输出样例】
17
【核心思想】
-
问题分析:给定 \(n\) 条线段,每条线段有起点 \(X_i\)、长度 \(W_i\)、价值 \(F_i\)、费用 \(C_i\)。要求选出若干线段铺满区间 \([0, L)\),首尾相接(不重叠不空缺),总费用不超过 \(B\),最大化总价值。这是一个01 背包 + 位置状态 DP 问题,核心在于将"覆盖位置"作为 DP 的一维状态,费用作为另一维,实现双约束下的最优化。
-
算法选择:
- 01 背包(二维 DP):\(f[pos][cost]\) 表示覆盖到位置 \(pos\)、花费 \(cost\) 费用时能获得的最大价值
- 排序预处理:按起点位置排序,确保处理时前面的线段先被考虑
-
关键步骤:
- 初始化:读取 \(L\)、\(n\)、\(B\)、\(n\) 条线段信息
- 排序:按起点 \(X_i\) 升序排序
- DP 初始化:\(f[pos][cost] = -\infty\)(不可达),\(f[0][0] = 0\)(边界)
- 01 背包递推:遍历每条线段 \((st, ed, cost, val)\):
- 倒序枚举费用 \(k\) 从 \(B\) 到 \(cost\):
- \(f[ed][k] = \max(f[ed][k], f[st][k-cost] + val)\)
- 前提:\(f[st][k-cost]\) 可达(即位置 \(st\) 已被覆盖)
- 倒序枚举费用 \(k\) 从 \(B\) 到 \(cost\):
- 提取答案:\(ans = \max_{0 \leq k \leq B} f[L][k]\)
- 输出:若 \(ans = -\infty\) 输出 \(-1\),否则输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times B)\),每条线段枚举费用 \(O(B)\)
- 空间复杂度:\(O(L \times B)\),二维 DP 数组
-
01 背包的核心思想:
- 位置作为状态维度:将"覆盖到哪个位置"纳入 DP 状态,确保线段首尾相接的约束被满足(只有 \(f[st]\) 可达时才能用起点为 \(st\) 的线段)
- 费用作为资源约束:第二维费用实现总费用不超过 \(B\) 的背包约束
- 倒序遍历保证 01:费用倒序枚举确保每条线段只被选一次
- 排序保证拓扑序:按起点排序后,处理线段 \(i\) 时所有能到达其起点的状态已计算完毕
- 适用于区间覆盖、双约束优化、位置状态 DP 类问题
【算法标签】
普及+ #01背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
const int N = 1005; // 最大区间长度和费用上限(实际应更大)
int L, n, B; // L:目标区间长度, n:线段数量, B:最大费用预算
struct Node
{
int x, w, f, c; // x:起点, w:长度, f:价值, c:费用
}a[10005]; // 线段数组
int f[N][N]; // f[pos][cost]:覆盖到位置pos,花费cost费用时能获得的最大价值
// 按起点位置排序
bool cmp(Node x, Node y)
{
return x.x < y.x;
}
int main()
{
cin >> L >> n >> B; // 读入目标长度、线段数和费用上限
for (int i=1; i<=n; i++) // 读入每条线段的属性
cin >> a[i].x >> a[i].w >> a[i].f >> a[i].c;
sort(a+1, a+n+1, cmp); // 按起点位置排序
// 初始化DP数组为负无穷,表示不可达状态
memset(f, -0x3f, sizeof(f));
f[0][0] = 0; // 边界条件:覆盖到位置0,花费0费用,价值为0
// 遍历每条线段
for (auto seg : a)
{
int st = seg.x, ed = seg.x + seg.w; // 线段的起点和终点
int cost = seg.c; // 该线段的费用
int val = seg.f; // 该线段的价值
// 倒序枚举费用(01背包,避免重复选取同一条线段)
for (int k=B; k>=cost; k--)
// 状态转移:如果该线段能接在已有覆盖的后面(st位置已被覆盖)
// 则更新ed位置的最大价值
f[ed][k] = max(f[ed][k], f[st][k-cost] + val);
}
// 在所有费用不超过B的方案中,找到覆盖到位置L的最大价值
int ans = -1e9;
for (int k=0; k<=B; k++)
ans = max(ans, f[L][k]);
// 如果没有任何方案能覆盖到位置L,输出-1
if (ans==-1e9)
cout << -1 << endl;
else cout << ans << endl; // 否则输出最大价值
return 0;
}
【运行结果】
5 6 10
0 2 20 6
2 3 5 6
0 1 2 1
1 1 1 3
1 2 5 4
3 2 10 2
17
浙公网安备 33010602011771号