题解:学而思编程 买一送一
【题目来源】
学而思编程:买一送一
【题目描述】
小明去一个比萨店买比萨。
店里有 \(n\) 种比萨出售,第 \(i\) 种的价格为 \(p_i\) 元,美味度为 \(d_i\)。比萨店正在开业酬宾,每购买 \(1\) 个比萨,就可以免费获得一个售价更小的比萨。
于是小明重复以下购买比萨的过程:
(1)选择一个比萨(假设是第 \(i\) 种),花费 \(p_i\) 元购买第 \(i\) 种比萨。
(2)从价格小于 \(p_i\) 元的比萨中选择一个,免费获得这个比萨。
小明只有 \(K\) 元,通过合理选择每次购买和免费获得的比萨,他最多可以得到的最大美味度的总和是多少?
【输入】
第 \(1\) 行,两个正整数 \(n,K\)
接下来 \(n\) 行,每行 \(2\) 个正整数 \(p_i,d_i\),表示第 \(i\) 种比萨的价格和美味度
【输出】
\(1\) 个整数,美味度总和的最大值
【输入样例】
3 64
15 17
5 6
33 39
【输出样例】
102
【核心思想】
-
问题分析:给定 \(n\) 种比萨,每种有价格 \(p_i\) 和美味度 \(d_i\),预算 \(K\) 元。每次购买一个比萨可以免费获得一个价格更小的比萨。目标是最大化总美味度。这是一个完全背包变体问题:每次购买比萨 \(i\) 时,除了获得 \(d_i\) 的美味度,还能免费获得价格小于 \(p_i\) 的比萨中美味度最大的那个。
-
算法选择:
- 排序预处理:按价格升序排序,价格相同则按美味度降序
- 预处理 maxd 数组:\(maxd[i]\) 表示价格小于 \(a[i].p\) 的物品中的最大美味度
- 完全背包动态规划:每种比萨可以无限次购买,但每次购买的实际收益为 \(d_i + maxd[i]\)
-
关键步骤:
-
Step 1 - 排序:
- 按价格 \(p_i\) 升序排序,价格相同则按美味度 \(d_i\) 降序
- 这样处理后,对于任意物品 \(i\),价格小于它的物品都在它前面
-
Step 2 - 预处理 maxd 数组:
- 对于每个物品 \(i\),遍历所有 \(j < i\) 且 \(a[j].p < a[i].p\) 的物品
- \(maxd[i] = \max(maxd[i], a[j].d)\),即价格小于 \(a[i].p\) 的物品中的最大美味度
-
Step 3 - 完全背包 DP:
- 状态定义:\(f[i][j]\) 表示考虑前 \(i\) 种物品,花费恰好为 \(j\) 时的最大美味度
- 不选第 \(i\) 种比萨:\(f[i][j] = f[i-1][j]\)
- 选第 \(i\) 种比萨(完全背包):若 \(j \geq a[i].p\),则 \(f[i][j] = \max(f[i][j], f[i][j-a[i].p] + a[i].d + maxd[i])\)
- 注意:每次购买的收益是 \(d_i + maxd[i]\)(购买的美味度 + 免费赠送的美味度)
-
Step 4 - 统计答案:
- 答案为所有 \(j \leq K\) 中 \(f[n][j]\) 的最大值
ans = max(ans, f[n][j])对于 \(j \in [0, K]\)
-
-
时间/空间复杂度:
- 时间复杂度:\(O(n^2 + n \times K)\),排序 \(O(n \log n)\),预处理 \(maxd\) 数组 \(O(n^2)\),完全背包 \(O(n \times K)\)
- 空间复杂度:\(O(n \times K)\),二维DP数组;可优化至 \(O(K)\) 使用一维数组
-
完全背包变体的核心思想:
- 问题转化:将"买一送一"的优惠转化为每次购买的实际收益 = 购买物品价值 + 赠送物品价值
- 排序策略:按价格排序后,价格小于当前物品的所有物品都在前面,便于预处理
- 预处理技巧:\(maxd[i]\) 数组预处理出每个价格档位能免费获得的最大美味度,避免DP过程中重复计算
- 状态转移:完全背包的标准转移,但价值计算时加上 \(maxd[i]\) 体现赠送优惠
- 答案处理:在预算范围内取最大值,不同于标准背包的恰好装满
- 适用于带优惠策略的背包问题、组合购买优化类问题
【算法标签】
完全背包
【代码详解】
#include <bits/stdc++.h>
using namespace std;
// ================= 常量与全局变量 =================
const int N = 305; // 最大物品数量
const int M = 10005; // 最大容量(预算上限)
int n; // 物品数量
int k; // 预算上限
int ans; // 最终答案
// 物品结构体
struct Node
{
int p; // 价格
int d; // 价值(满意度)
} a[N];
int f[N][M]; // 动态规划数组
// f[i][j] 表示考虑前 i 个物品,花费恰好为 j 时的最大价值
int maxd[N]; // maxd[i] 表示在价格小于 a[i].p 的物品中,最大的价值
// ================= 排序比较函数 =================
bool cmp(Node x, Node y)
{
if (x.p != y.p) // 先按价格升序排序
return x.p < y.p;
return x.d > y.d; // 价格相同时,按价值降序排序
}
// ================= 主函数 =================
int main()
{
// 读取物品数量和预算上限
cin >> n >> k;
// 读取每个物品的价格和价值
for (int i = 1; i <= n; i++)
cin >> a[i].p >> a[i].d;
// 按价格升序、价值降序排序
sort(a + 1, a + n + 1, cmp);
// 预处理 maxd 数组
// 对于每个物品 i,找到价格比它小的物品中的最大价值
for (int i = 1; i <= n; i++)
for (int j = 1; j < i; j++)
if (a[j].p < a[i].p)
maxd[i] = max(maxd[i], a[j].d);
// 初始化边界条件
f[0][0] = 0;
// 完全背包 DP
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= k; j++)
{
// 不选第 i 个物品
f[i][j] = f[i - 1][j];
// 选第 i 个物品(完全背包,可重复选择)
if (j >= a[i].p)
{
// 价值 = 当前物品的价值 + 价格更低物品中的最大价值
f[i][j] = max(f[i][j], f[i][j - a[i].p] + a[i].d + maxd[i]);
}
}
}
// 统计答案:在所有花费不超过预算的状态中取最大价值
for (int j = 0; j <= k; j++)
ans = max(ans, f[n][j]);
// 输出结果
cout << ans << endl;
return 0;
}
【运行结果】
3 64
15 17
5 6
33 39
102
浙公网安备 33010602011771号