题解:学而思编程 点菜

【题目来源】

学而思编程:点菜

【题目描述】

小A来吃自助餐。店里提供 \(N\) 道菜,吃第 \(i\) 道菜需要 \(A_i\) 分钟,美味度是 \(B_i\)

店里的规则如下:

  1. 每次只能点一道菜。
  2. 每道菜只能点一次。
  3. 必须把之前点的菜吃完才能点下一道菜。
  4. 从进店后第 \(T\) 分钟开始就不能点菜了,但是还可以吃之前点好的菜。

求小A可以吃的菜的最大美味度总和。假设小A进店马上就点了他的第一道菜,忽略点菜上菜的时间。

【输入】

\(1\) 行,\(2\) 个正整数 \(n,T\)

接下来 \(n\) 行,每行两个正整数 \(A_i,B_i\)

【输出】

输出小A可以吃到的菜的美味度总和的最大值。

【输入样例】

3 60
10 10
100 100
100 80

【输出样例】

110

【核心思想】

  1. 问题分析:给定 \(N\) 道菜,吃第 \(i\) 道菜需要 \(A_i\) 分钟,美味度是 \(B_i\)。规则:每次只能点一道菜、每道菜只能点一次、必须吃完才能点下一道、第 \(T\) 分钟后不能点菜但还能吃。求最大美味度总和。这是一个01背包变体问题,关键在于第 \(T\) 分钟后不能点菜但还能吃,意味着最后一道菜必须在第 \(T\) 分钟前点好,它需要1分钟点菜时间,实际吃菜时间为 \(A_i - 1\)

  2. 算法选择

    • 前缀DPf[i][j] 表示前 \(i\) 道菜,时间不超过 \(j\) 的最大美味值
    • 后缀DPg[i][j] 表示从第 \(i\) 道到第 \(n\) 道菜,时间不超过 \(j\) 的最大美味值
    • 枚举分割点:枚举哪道菜作为"最后点的那道"
  3. 关键步骤

    • 前缀DP计算:标准01背包,f[i][j] = max(f[i-1][j], f[i-1][j-A[i]] + B[i])
    • 后缀DP计算:逆向01背包,g[i][j] = max(g[i+1][j], g[i+1][j-A[i]] + B[i])
    • 枚举最后点的菜 \(i\)
      • 枚举分配给前半部分的时间 \(j\)\(0 \leq j \leq t-1\),留1分钟给第 \(i\) 道菜)
      • 后半部分时间:t - 1 - j
      • 总价值:f[i-1][j] + g[i+1][t-1-j] + B[i]
    • 统计答案:取所有分割方案的最大值
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \times T)\),前缀DP \(O(N \times T)\),后缀DP \(O(N \times T)\),枚举 \(O(N \times T)\)
    • 空间复杂度:\(O(N \times T)\),两个DP数组
  5. 前后缀DP枚举分割点的核心思想

    • 问题转化:将"最后点的菜"作为分割点,问题分解为前缀 + 中间点 + 后缀
    • 前后缀预处理:分别计算前缀和后缀的01背包最优解
    • 枚举策略:枚举每个可能的"最后点的菜",计算最优分割
    • 时间分配:总时间 \(T\) 中,1分钟给最后点的菜,剩余 \(T-1\) 分钟分配给前后两部分
    • 适用于"选择一个特殊点分割序列"的背包问题

【算法标签】

01背包

【代码详解】

#include<iostream>
using namespace std;
const int N = 3005;
int n, t, a[N], b[N], f[N][N], g[N][N], ans;  // f: 前i个物品的前缀DP, g: 后i个物品的后缀DP

int main()
{
    cin >> n >> t;
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i] >> b[i];  // 时间消耗, 美味值
    }

    // 计算前缀DP: f[i][j]表示前i个物品,时间不超过j的最大美味值
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j < t; j++)
        {
            f[i][j] = f[i - 1][j];  // 不选第i个物品
            if (j >= a[i])
            {
                f[i][j] = max(f[i][j], f[i - 1][j - a[i]] + b[i]);  // 选第i个物品
            }
        }
    }

    // 计算后缀DP: g[i][j]表示从第i个到第n个物品,时间不超过j的最大美味值
    for (int i = n; i >= 1; i--)
    {
        for (int j = 0; j < t; j++)
        {
            g[i][j] = g[i + 1][j];  // 不选第i个物品
            if (j >= a[i])
            {
                g[i][j] = max(g[i][j], g[i + 1][j - a[i]] + b[i]);  // 选第i个物品
            }
        }
    }

    // 枚举最后一个吃的菜品i
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= t; j++)  // 分配给前半部分的时间
        {
            // 总美味值 = 前i-1个物品的美味值 + 后i+1个物品的美味值 + 第i个物品的美味值
            // 第i个菜品需要1单位时间,所以剩余时间是t-1
            ans = max(ans, f[i - 1][j] + g[i + 1][t - 1 - j] + b[i]);
        }
    }
    cout << ans;
    return 0;
}

【运行结果】

3 60
10 10
100 100
100 80
110
posted @ 2026-06-14 10:47  团爸讲算法  阅读(11)  评论(0)    收藏  举报