用贪心算法解决‘金银岛’问题:信息学奥赛经典题保姆级拆解(附C++代码)
·
用贪心算法解决‘金银岛’问题:信息学奥赛经典题保姆级拆解(附C++代码)
第一次接触金银岛问题时,许多初学者会被"任意分割"这个条件所迷惑——这和传统的背包问题有什么区别?为什么不能直接按总价值排序?作为信息学奥赛中的经典贪心算法案例,这道题完美展现了如何通过局部最优选择达到全局最优解。本文将用"问题拆解→策略推导→代码实现→边界处理"四步法,带你彻底掌握这类问题的思考模式。
1. 问题本质与贪心策略构建
金银岛问题的核心在于理解"可分割物品"带来的算法选择差异。与0-1背包不同,金属可以按需切割的特性,决定了我们不需要考虑物品的完整性,只需要关注单位价值密度。
1.1 关键条件分析
- 任意分割 :允许取金属的一部分而非整个物品
- 价值重量比 :每单位重量的价值(v_i/n_i)决定优先级
- 无组合限制 :不需要考虑物品间的搭配关系
注意:不可分割物品(如传统背包问题)通常需要动态规划,而可分割物品往往适用贪心算法
1.2 贪心策略证明
为什么单位价值排序是最优解?我们可以用反证法:
假设存在更优方案未按单位价值降序选取,那么必然存在两个相邻物品i和j,其中:
- v_i/n_i < v_j/n_j
- 方案中i被部分选取而j未被完全选取
此时若将i的部分重量调整为选取j,则总价值会增加,与"最优"矛盾。因此原始假设不成立。
2. 数据结构与排序实现
2.1 结构体设计
需要同时存储金属的重量和价值,并支持按单位价值排序:
struct Metal {
int total_weight; // n_i
int total_value; // v_i
};
2.2 自定义比较函数
关键是比较v_i/n_i和v_j/n_j的大小,为避免浮点精度问题,使用交叉相乘:
bool compare(const Metal& a, const Metal& b) {
return a.total_value * b.total_weight >
b.total_value * a.total_weight;
}
2.3 排序调用
vector<Metal> metals;
sort(metals.begin(), metals.end(), compare);
3. 核心算法流程实现
3.1 算法步骤分解
- 读取所有金属数据
- 按单位价值降序排序
- 遍历排序后的金属列表:
- 能全装下则全部装入
- 装不下则装入部分直至背包装满
- 累加计算总价值
3.2 完整代码框架
#include <iostream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;
struct Metal { /* 同上 */ };
int main() {
int k, w, s;
cin >> k;
while (k--) {
cin >> w >> s;
vector<Metal> metals(s);
for (int i = 0; i < s; ++i) {
cin >> metals[i].total_weight
>> metals[i].total_value;
}
sort(metals.begin(), metals.end(), compare);
double max_value = 0.0;
for (const auto& m : metals) {
if (w <= 0) break;
int take = min(w, m.total_weight);
max_value += take * (m.total_value * 1.0 / m.total_weight);
w -= take;
}
cout << fixed << setprecision(2) << max_value << endl;
}
return 0;
}
4. 关键细节与常见错误
4.1 浮点数精度处理
- 在比较阶段使用整数运算避免精度损失
- 只在最终计算时使用浮点数:
// 正确做法
double value = weight * (total_value * 1.0 / total_weight);
// 错误做法(可能导致精度丢失)
double unit_value = total_value / total_weight;
double value = weight * unit_value;
4.2 边界条件
- 背包容量为0时的直接返回
- 所有金属总重量小于背包容量时的完全装载
- 重量和价值的整数溢出问题(题目给定范围无需处理)
4.3 测试用例验证
建议自测以下特殊场景:
1. 背包容量为0
2. 只有一种金属
3. 所有金属单位价值相同
4. 金属总重量小于背包容量
5. 算法扩展与变种思考
虽然金银岛问题使用贪心算法效果很好,但了解其局限性也很重要:
| 问题变种 | 适用算法 | 原因 |
|---|---|---|
| 不可分割物品 | 动态规划 | 需要考虑物品完整性 |
| 有数量限制 | 多重背包 | 组合复杂度增加 |
| 多维约束 | 多维DP | 需要考虑多个容量维度 |
在实际比赛中,建议先明确题目是否包含以下关键词:
- "可以任意分割" → 贪心算法
- "必须完整取用" → 动态规划
- "每种物品有限个" → 多重背包
理解了这个问题的解法后,可以尝试解决NOIP中的"合并果子"问题,体会不同场景下贪心策略的应用差异。
更多推荐

所有评论(0)