1. 题目背景与需求分析
洛谷P2676 [USACO07DEC] Bookshelf B是一道经典的贪心算法练习题,源自美国计算机奥林匹克竞赛(USACO)2007年12月月赛。题目描述了一个农夫需要为他的奶牛建造书架的问题:
农夫约翰有N头奶牛(1 ≤ N ≤ 20,000),每头奶牛都有一个确定的身高H_i(1 ≤ H_i ≤ 10,000)。他需要选择其中的一些奶牛叠起来(一头站在另一头的背上),使得这些奶牛的总高度至少达到书架的高度B(1 ≤ B ≤ 2,000,000)。我们的目标是找出需要的最少奶牛数量。
这个问题的核心在于:如何在满足高度要求的前提下,用最少数量的奶牛。这引导我们自然地想到贪心算法的解决方案——优先选择身高较高的奶牛。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 解题思路与算法选择
2.1 贪心算法原理
贪心算法在每一步选择中都采取当前状态下最优的选择,从而希望导致全局最优的结果。对于本题而言,要使使用的奶牛数量最少,显然应该优先选择身高最高的奶牛,这样可以用更少的奶牛达到要求的高度。
贪心算法适用于本题的关键特性:
- 问题具有最优子结构:局部最优解能导致全局最优解
- 贪心选择性质:每一步的最优选择不依赖于子问题的解
2.2 具体实现步骤
- 输入处理:读取奶牛数量N和目标高度B
- 数据存储:将每头奶牛的身高存入数组
- 排序:将奶牛按身高从高到低排序
- 累加计算:从最高的奶牛开始累加身高,直到达到或超过B
- 输出结果:统计使用的奶牛数量
3. 代码实现与详细解析
3.1 C++实现代码
cpp复制#include <iostream>
#include <algorithm>
using namespace std;
int main() {
int N, B;
cin >> N >> B;
int H[N];
for(int i = 0; i < N; i++) {
cin >> H[i];
}
// 从大到小排序
sort(H, H + N, greater<int>());
int sum = 0, count = 0;
for(int i = 0; i < N; i++) {
sum += H[i];
count++;
if(sum >= B) {
break;
}
}
cout << count << endl;
return 0;
}
3.2 代码逐行解析
#include <algorithm>:包含STL算法库,用于排序操作sort(H, H + N, greater<int>()):将数组H从大到小排序greater<int>()是STL提供的比较函数对象,实现降序排序
- 累加循环:从最高的奶牛开始累加,直到满足高度要求
- 提前终止:一旦达到要求立即退出循环,提高效率
3.3 时间复杂度分析
- 排序操作:使用STL的sort函数,时间复杂度为O(N log N)
- 累加操作:最坏情况下O(N)
- 总体复杂度:O(N log N),对于N≤20,000完全在可接受范围内
4. 算法优化与边界情况
4.1 可能的优化方向
- 使用更快的排序算法:虽然STL的sort已经很高效,但对于特定数据范围可以考虑计数排序
- 提前终止条件优化:可以在循环中加入更精确的判断条件
- 输入输出优化:对于大规模数据,使用更快的IO方法
4.2 边界情况处理
- 所有奶牛身高总和仍不足B:题目保证有解,实际编程中可不处理
- 单头奶牛身高已超过B:循环会在第一次迭代后立即终止
- 高度刚好等于B:
sum >= B的条件会正确处理
4.3 测试用例设计
好的测试用例应包含:
- 常规情况:多头奶牛,需要组合
- 极端情况:只需一头奶牛或需要全部奶牛
- 边界值:总高度刚好等于B
- 大量数据:测试算法效率
示例测试用例:
code复制输入:
5 16
3 1 5 7 6
输出:
3
5. 同类问题与扩展思考
5.1 USACO中的类似题目
- P1208 [USACO1.3]混合牛奶 Mixing Milk:同样是贪心算法应用
- P1094 [USACO07DEC]纪念品分组:类似的组合优化问题
- P2695 [USACO09DEC]Cow Toll Paths:更复杂的贪心应用
5.2 算法扩展应用
贪心算法在实际中有广泛应用:
- 霍夫曼编码:数据压缩
- 最小生成树:Prim和Kruskal算法
- 最短路径:Dijkstra算法
- 任务调度:选择结束时间最早的任务
5.3 反例与贪心算法的局限性
不是所有问题都适合贪心算法。例如背包问题中,如果物品可以分割,贪心算法有效;但对于0-1背包问题,贪心算法可能得不到最优解。判断一个问题是否适合用贪心算法,需要验证其是否具有贪心选择性质和最优子结构。
6. 洛谷刷题建议与技巧
6.1 USACO题目特点
- 输入输出格式严格:必须完全按照要求
- 数据规模明确:可以帮助选择算法
- 实际问题背景:常以农场、奶牛为场景
- 多考察基础算法:排序、贪心、搜索等
6.2 高效刷题方法
- 先理解题意:画图、举例帮助理解
- 设计算法:不急于编码,先设计解决方案
- 编写代码:注意代码规范和边界条件
- 测试调试:使用样例和自建测试用例
- 优化改进:分析时间空间复杂度
6.3 调试技巧
- 使用小规模数据测试
- 添加调试输出语句
- 利用IDE的调试功能
- 对比他人正确代码
- 分析错误类型:WA、TLE、RE等
7. 从题目到竞赛的进阶路径
7.1 算法学习路线
- 基础语法:熟练掌握一门语言(C++/Java/Python)
- 数据结构:数组、链表、栈、队列、树、图
- 基础算法:排序、查找、贪心、分治
- 高级算法:动态规划、图论、数论
- 专项突破:针对薄弱环节重点练习
7.2 竞赛准备建议
- 定期参加比赛:如Codeforces、AtCoder
- 专题训练:集中攻克某一类问题
- 模拟赛:全真模拟比赛环境
- 错题整理:分析错误原因
- 学习优秀代码:吸收他人优点
7.3 在线评测平台推荐
- 洛谷:中文友好,题目丰富
- Codeforces:比赛频繁,社区活跃
- AtCoder:日本平台,题目质量高
- LeetCode:适合面试准备
- USACO官网:官方题目与题解
8. 个人实战经验分享
在实际解决这个问题时,有几个关键点值得注意:
-
排序方向容易出错:要明确是需要升序还是降序排列。本题需要降序排列,使用greater
()或者自定义比较函数都可以实现。 -
累加终止条件:使用sum >= B而不是sum > B,因为题目要求"至少"达到B高度。这个细节在编程竞赛中经常成为失分点。
-
变量初始化:sum和count变量必须初始化为0,否则可能导致不可预知的结果。这是新手常犯的错误。
-
输入规模考虑:虽然题目给出的N上限是20,000,但在实际比赛中,养成处理大规模数据的习惯很重要。这意味着要避免使用时间复杂度太高的算法。
-
代码简洁性:在竞赛中,简洁清晰的代码不仅能减少错误,还能节省时间。例如本题中,可以在输入的同时进行累加和判断,进一步简化代码。
对于想要进一步提高的同学,我建议在解决这个问题后,尝试它的变种问题,比如:
- 如果每头奶牛不仅有高度还有重量,书架有承重限制,如何选择?
- 如果允许奶牛站在多层的结构上(不只是一头站在另一头背上),解法会有什么变化?
- 如果奶牛的高度可以是负数(表示"挖洞"),问题该如何解决?
这些扩展思考能帮助你更深入地理解贪心算法的应用场景和局限性。
