题解:学而思编程 买一送一

【题目来源】

学而思编程:买一送一

【题目描述】

小明去一个比萨店买比萨。

店里有 \(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

【核心思想】

  1. 问题分析:给定 \(n\) 种比萨,每种有价格 \(p_i\) 和美味度 \(d_i\),预算 \(K\) 元。每次购买一个比萨可以免费获得一个价格更小的比萨。目标是最大化总美味度。这是一个完全背包变体问题:每次购买比萨 \(i\) 时,除了获得 \(d_i\) 的美味度,还能免费获得价格小于 \(p_i\) 的比萨中美味度最大的那个。

  2. 算法选择

    • 排序预处理:按价格升序排序,价格相同则按美味度降序
    • 预处理 maxd 数组\(maxd[i]\) 表示价格小于 \(a[i].p\) 的物品中的最大美味度
    • 完全背包动态规划:每种比萨可以无限次购买,但每次购买的实际收益为 \(d_i + maxd[i]\)
  3. 关键步骤

    • 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]\)
  4. 时间/空间复杂度

    • 时间复杂度:\(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)\) 使用一维数组
  5. 完全背包变体的核心思想

    • 问题转化:将"买一送一"的优惠转化为每次购买的实际收益 = 购买物品价值 + 赠送物品价值
    • 排序策略:按价格排序后,价格小于当前物品的所有物品都在前面,便于预处理
    • 预处理技巧\(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
posted @ 2026-06-17 09:28  团爸讲算法  阅读(7)  评论(0)    收藏  举报