CCF-GESP六级C++真题精讲:用‘01背包’思路搞定‘小杨买饮料’(附完整代码)
2026/6/11 10:12:31 网站建设 项目流程

CCF-GESP六级C++真题精讲:用‘01背包’思路搞定‘小杨买饮料’(附完整代码)

当算法竞赛选手第一次看到"小杨买饮料"这道题时,往往会陷入两种极端:要么觉得这不过是个简单的贪心选择问题,要么被题目中"总容量不低于L"的条件吓得手足无措。实际上,这道来自CCF-GESP六级考试的真题完美诠释了动态规划在实际问题中的巧妙应用——它表面上是个购物问题,骨子里却是个标准的01背包变种。

1. 题目本质与算法选择

题目描述看似简单:商店有N种饮料,每种有价格和容量,要求购买总容量≥L的前提下花费最少,且每种饮料最多买一瓶。新手容易犯的错误是直接按"性价比"(每毫升价格)排序后贪心选择,但样例1已经证明这种思路的错误——最优解9元对应的方案(2,4,3元)并非性价比最高的组合。

关键突破点在于识别出三个核心特征

  1. 离散决策:每种饮料只有买/不买两种选择
  2. 累积效应:总容量是各饮料容量的叠加
  3. 最优子结构:当前决策影响后续状态

这三点正是动态规划(特别是01背包)的典型适用场景。但与经典背包问题不同的是:

特征经典01背包小杨买饮料
限制条件容量≤上限容量≥下限
目标价值最大化花费最小化
边界处理超容即无效超容仍有效

2. 动态规划状态设计

解决这个问题的核心是设计合适的状态表示。我们定义:

  • dp[j]:获得至少j毫升饮料的最小花费
  • 初始状态:dp[0] = 0(0毫升不需要花钱),其余设为无穷大(表示不可达)

状态转移方程需要考虑题目允许"超额满足"的特殊条件:

dp[j] = min(dp[j], dp[max(j - l, 0)] + c);

其中max(j - l, 0)的处理是关键——当当前饮料容量l已经满足剩余需求j时,我们直接从dp[0]转移而来。

实现细节注意点

  • 内循环需要倒序枚举j,避免同一饮料被多次选择
  • 最终解是dp[L],若仍为初始值则输出无解
  • 数组大小应设为L+1而非饮料总容量上限

3. 完整代码实现与逐行解析

以下是带详细注释的AC代码,严格遵循CCF考试提交规范:

#include <iostream> #include <algorithm> using namespace std; const int INF = 1e9; // 定义足够大的数代表"无穷大" int main() { int N, L; cin >> N >> L; int* dp = new int[L + 1]; // 动态数组更灵活 fill(dp, dp + L + 1, INF); // C++标准库填充函数 dp[0] = 0; // 基础状态初始化 for (int i = 0; i < N; ++i) { int cost, volume; cin >> cost >> volume; // 逆向更新防止重复选择 for (int j = L; j >= 0; --j) { int prev = max(j - volume, 0); dp[j] = min(dp[j], dp[prev] + cost); } } if (dp[L] == INF) { cout << "no solution" << endl; } else { cout << dp[L] << endl; } delete[] dp; // 释放动态数组 return 0; }

关键代码段解析

  1. fill(dp, dp + L + 1, INF):比循环赋值更高效的初始化方式
  2. prev = max(j - volume, 0):处理超额满足的核心逻辑
  3. j--的遍历顺序:确保每种饮料只被考虑一次
  4. 动态数组管理:适应不同规模的L值

4. 算法复杂度与优化空间

该解法的时间复杂度为O(N*L),空间复杂度O(L)。对于GESP考试的数据规模(通常N,L≤1000)完全足够。但在实际竞赛中,可以考虑以下优化方向:

滚动数组技巧

vector<int> dp(L + 1, INF); dp[0] = 0; for (auto& drink : drinks) { for (int j = L; j >= 0; --j) { int prev = max(j - drink.vol, 0); dp[j] = min(dp[j], dp[prev] + drink.cost); } }

常见错误排查表

错误现象可能原因解决方法
输出结果偏大正序更新导致重复选择改为逆序更新
部分样例WA未处理超额满足条件添加max(j-l,0)处理
内存超限数组开得过大精确计算L+1空间
时间超限使用vector未reserve预分配内存

5. 同类问题拓展与思维训练

掌握这个模型后,可以解决一系列变种问题:

  1. 恰好满足版:要求总容量严格等于L

    • 修改:初始化时只设dp[0]=0,转移时去掉max处理
  2. 多重选择版:每种饮料可买多瓶

    • 解法:将内循环改为正序,转化为完全背包问题
  3. 双限制条件:同时限制总容量和总瓶数

    • 状态设计:dp[j][k]表示j容量k瓶时的最小花费

推荐练习题目

  • LeetCode 416(分割等和子集)
  • POJ 3624(经典01背包)
  • AtCoder DP Contest D(变种背包)

在实际编程竞赛中,动态规划的难点往往不在于写出状态方程,而在于准确识别问题背后的模型。建议通过以下步骤训练:

  1. 将问题描述中的关键参数提取为"物品"和"容量"
  2. 明确决策选项(选/不选,选多少次)
  3. 确定目标是最小化还是最大化
  4. 处理特殊边界条件(如本题的超额满足)

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询