1. 题目背景与核心考察点
Mooncake这道PAT甲级真题出现在2019年秋季考试中,题目编号为1020。作为一道25分值的题目,它典型地考察了贪心算法在实际商业场景中的应用能力。我在第一次刷这道题时,花了近40分钟才完全通过所有测试用例,后来通过反复练习总结出了一套高效的解题模式。
这道题的背景设定在中秋节月饼销售场景,要求考生处理不同种类月饼的库存和售价,计算在给定市场需求量下的最大销售收益。题目输入包含:
- N(月饼种类数,≤1000)
- D(市场需求总量,≤500吨)
- 每种月饼的库存量(万吨单位)
- 每种月饼的总售价(亿元单位)
2. 数据结构设计与贪心策略
2.1 月饼结构体定义
首先需要设计合适的数据结构来存储月饼信息。我推荐使用结构体数组:
cpp复制struct Mooncake {
double inventory; // 库存量(万吨)
double totalPrice; // 总售价(亿元)
double unitPrice; // 单价(亿元/万吨)
} cakes[1010];
这里使用double类型是因为题目中涉及除法运算可能产生小数。unitPrice字段需要通过计算得到,这是贪心算法的关键依据。
2.2 贪心算法策略解析
这道题的核心是典型的分数背包问题,贪心策略应该按照单价从高到低排序:
- 计算每种月饼的单价(总售价/库存量)
- 将月饼按单价降序排列
- 优先销售单价高的月饼,直到满足市场需求
这个策略的数学证明很简单:假设存在更高收益的非贪心选择,通过交换可以得到更大收益,与假设矛盾。
3. 关键实现细节与优化
3.1 排序算法的选择
虽然题目数据量不大(N≤1000),但为了养成良好习惯,我建议使用O(nlogn)的排序:
cpp复制bool cmp(Mooncake a, Mooncake b) {
return a.unitPrice > b.unitPrice;
}
sort(cakes, cakes+N, cmp);
注意比较函数要返回bool类型,这是很多新手容易出错的地方。
3.2 浮点数精度处理
题目要求输出保留2位小数,需要特别注意:
- 计算单价时不要过早舍入
- 最终输出前再进行四舍五入
- 比较浮点数时使用相减与epsilon比较
推荐使用printf控制输出格式:
cpp复制printf("%.2f", totalProfit);
3.3 边界条件处理
实际编码时需要特别注意:
- 库存总量可能小于市场需求量
- 某种月饼库存可能为0(虽然题目说库存为正)
- 市场需求量D为0的情况
4. 完整参考代码与注释
cpp复制#include <iostream>
#include <algorithm>
using namespace std;
struct Mooncake {
double inventory;
double totalPrice;
double unitPrice;
} cakes[1010];
bool cmp(Mooncake a, Mooncake b) {
return a.unitPrice > b.unitPrice;
}
int main() {
int N, D;
cin >> N >> D;
for(int i=0; i<N; i++) {
cin >> cakes[i].inventory;
}
for(int i=0; i<N; i++) {
cin >> cakes[i].totalPrice;
cakes[i].unitPrice = cakes[i].totalPrice / cakes[i].inventory;
}
sort(cakes, cakes+N, cmp);
double totalProfit = 0.0;
double remaining = D;
for(int i=0; i<N && remaining>0; i++) {
if(cakes[i].inventory <= remaining) {
totalProfit += cakes[i].totalPrice;
remaining -= cakes[i].inventory;
} else {
totalProfit += cakes[i].unitPrice * remaining;
remaining = 0;
}
}
printf("%.2f", totalProfit);
return 0;
}
5. 常见错误与调试技巧
5.1 典型错误案例
- 整数除法问题:
cpp复制// 错误写法
unitPrice = totalPrice / inventory; // 如果都是整数会丢失精度
// 正确写法
unitPrice = (double)totalPrice / inventory;
- 排序函数错误:
cpp复制// 错误写法 - 返回int
int cmp(Mooncake a, Mooncake b) {
return a.unitPrice > b.unitPrice;
}
// 正确写法 - 返回bool
bool cmp(Mooncake a, Mooncake b) {
return a.unitPrice > b.unitPrice;
}
5.2 测试用例设计
建议自测时使用以下案例:
code复制// 常规案例
3 20
18 15 10
75 72 45
// 边界案例1 - 库存不足
2 100
10 20
50 60
// 边界案例2 - 零需求
3 0
1 2 3
4 5 6
5.3 性能优化建议
虽然题目数据量不大,但可以做一些优化准备:
- 使用快速输入方法(如cin关闭同步)
- 避免不必要的拷贝(结构体传引用)
- 提前终止循环(剩余需求为0时退出)
6. 算法扩展与变种思考
这道题的贪心算法可以扩展到许多实际场景:
- 投资组合优化(有限资金下选择最高回报率项目)
- 资源分配问题(CPU时间片分配)
- 货物装载问题(集装箱装货)
一个有趣的变种是加入月饼保质期约束,这时问题就变成了带时间窗的背包问题,难度会提升到动态规划层面。
在实际刷题过程中,我建议把这道题与PAT的"Radix"(1010)和"Median"(1029)对比练习,它们都考察了类似的边界条件处理能力。记住,PAT甲级的25分题目往往在简单的表面下隐藏着一些陷阱,需要格外仔细。
