题解:洛谷 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

【核心思想】

  1. 问题分析:给定 \(n\) 条线段,每条线段有起点 \(X_i\)、长度 \(W_i\)、价值 \(F_i\)、费用 \(C_i\)。要求选出若干线段铺满区间 \([0, L)\),首尾相接(不重叠不空缺),总费用不超过 \(B\),最大化总价值。这是一个01 背包 + 位置状态 DP 问题,核心在于将"覆盖位置"作为 DP 的一维状态,费用作为另一维,实现双约束下的最优化。

  2. 算法选择

    • 01 背包(二维 DP)\(f[pos][cost]\) 表示覆盖到位置 \(pos\)、花费 \(cost\) 费用时能获得的最大价值
    • 排序预处理:按起点位置排序,确保处理时前面的线段先被考虑
  3. 关键步骤

    • 初始化:读取 \(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\) 已被覆盖)
    • 提取答案\(ans = \max_{0 \leq k \leq B} f[L][k]\)
    • 输出:若 \(ans = -\infty\) 输出 \(-1\),否则输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times B)\),每条线段枚举费用 \(O(B)\)
    • 空间复杂度:\(O(L \times B)\),二维 DP 数组
  5. 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
posted @ 2026-08-13 14:14  团爸讲算法  阅读(5)  评论(0)    收藏  举报