题解:学而思编程 点菜
【题目来源】
学而思编程:点菜
【题目描述】
小A来吃自助餐。店里提供 \(N\) 道菜,吃第 \(i\) 道菜需要 \(A_i\) 分钟,美味度是 \(B_i\)。
店里的规则如下:
- 每次只能点一道菜。
- 每道菜只能点一次。
- 必须把之前点的菜吃完才能点下一道菜。
- 从进店后第 \(T\) 分钟开始就不能点菜了,但是还可以吃之前点好的菜。
求小A可以吃的菜的最大美味度总和。假设小A进店马上就点了他的第一道菜,忽略点菜上菜的时间。
【输入】
第 \(1\) 行,\(2\) 个正整数 \(n,T\)
接下来 \(n\) 行,每行两个正整数 \(A_i,B_i\)
【输出】
输出小A可以吃到的菜的美味度总和的最大值。
【输入样例】
3 60
10 10
100 100
100 80
【输出样例】
110
【核心思想】
-
问题分析:给定 \(N\) 道菜,吃第 \(i\) 道菜需要 \(A_i\) 分钟,美味度是 \(B_i\)。规则:每次只能点一道菜、每道菜只能点一次、必须吃完才能点下一道、第 \(T\) 分钟后不能点菜但还能吃。求最大美味度总和。这是一个01背包变体问题,关键在于第 \(T\) 分钟后不能点菜但还能吃,意味着最后一道菜必须在第 \(T\) 分钟前点好,它需要1分钟点菜时间,实际吃菜时间为 \(A_i - 1\)。
-
算法选择:
- 前缀DP:
f[i][j]表示前 \(i\) 道菜,时间不超过 \(j\) 的最大美味值 - 后缀DP:
g[i][j]表示从第 \(i\) 道到第 \(n\) 道菜,时间不超过 \(j\) 的最大美味值 - 枚举分割点:枚举哪道菜作为"最后点的那道"
- 前缀DP:
-
关键步骤:
- 前缀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]
- 统计答案:取所有分割方案的最大值
- 前缀DP计算:标准01背包,
-
时间/空间复杂度:
- 时间复杂度:\(O(N \times T)\),前缀DP \(O(N \times T)\),后缀DP \(O(N \times T)\),枚举 \(O(N \times T)\)
- 空间复杂度:\(O(N \times T)\),两个DP数组
-
前后缀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
浙公网安备 33010602011771号