GESP 7级

贪心策略

7级 · DP/图论/搜索

📖 一句话理解

贪心(Greedy)就是每一步都选当前看起来最好的,不做长远考虑,也不回头。就像你在自助餐里,每次都拿眼前最想吃的菜,不考虑后面还有没有更好的。
💡 为什么重要?
贪心是很多经典算法的核心思想:Dijkstra最短路、Kruskal最小生成树、哈夫曼编码、活动选择……都是基于贪心。但贪心不一定能得到全局最优解,只有满足特定条件的问题才能用贪心。学会判断一个问题能不能用贪心,是7级考试的重要考点。
📋 前置知识
  • 排序:sort()函数的使用,以及自定义排序(lambda表达式)
  • 基本的逻辑推理:能理解"为什么这样贪是对的"
  • 了解 背包问题 会有帮助(对比贪心和DP的区别)

🧠 贪心的两个条件

🔑 什么时候能用贪心?
  1. 贪心选择性质:局部最优选择能导致全局最优。即"每步选最好的,最终结果就是最好的"。
  2. 最优子结构:问题的最优解包含子问题的最优解。

⚠️ 注意:不是所有问题都满足这两个条件!不满足时贪心会得到错误答案,需要用DP或搜索。

📐 经典贪心问题

问题1:区间调度(选最多的不重叠区间)

有n个时间段的活动,每个活动有开始时间和结束时间。选最多的活动,使得它们互不重叠。

💡 贪心策略
按结束时间排序,每次选结束最早的活动。为什么?结束越早,留给后面的空间越大。
💻 区间调度代码(逐行注释)
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

struct Interval {
    int start, end;  // 活动的开始和结束时间
};

int main() {
    int n;
    cin >> n;
    vector<Interval> intervals(n);
    for (int i = 0; i < n; i++)
        cin >> intervals[i].start >> intervals[i].end;

    // 贪心策略:按结束时间从小到大排序
    sort(intervals.begin(), intervals.end(),
        [](const Interval& a, const Interval& b) {
            return a.end < b.end;  // 结束早的排前面
        });

    int cnt = 0;           // 已选活动数量
    int last_end = -1;     // 上一个选中活动的结束时间

    for (auto& p : intervals) {
        // 如果当前活动的开始时间 >= 上一个活动的结束时间
        // 说明不重叠,可以选
        if (p.start >= last_end) {
            cnt++;              // 选这个活动
            last_end = p.end;  // 更新结束时间
        }
        // 否则跳过(重叠了,不能选)
    }

    cout << "最多能选 " << cnt << " 个活动" << endl;
    return 0;
}

问题2:零钱兑换(贪心不一定对!)

⚠️ 贪心的陷阱:零钱兑换

硬币面值 [1, 3, 4],凑出6元。

贪心:先选最大的4 → 再选1+1 → 共3枚

最优:选3+3 → 只要2枚

❌ 贪心在这里不是最优的!因为贪心选择性质不满足。这种问题需要用DP。

📊 贪心 vs DP vs 暴力搜索

📝 三种方法对比
  • 暴力搜索:尝试所有可能,时间指数级,正确但太慢
  • 贪心:每步选最好的,时间快(通常O(n log n)),但不一定正确
  • 动态规划:记录子问题答案,时间多项式级,保证正确

做题时的思考顺序:先想贪心能不能用(证明一下)→ 不能就用DP → 再不行就搜索。

🎯 常见贪心策略

💡 需要记住的贪心模式
  • 区间调度:按结束时间排序,选最早结束的
  • 活动选择:和区间调度类似
  • 分数背包:按单位价值(价值/重量)排序,从高到低选(注意:01背包不能贪心!分数背包可以)
  • 任务调度:按截止时间排序,优先做紧急的

🚨 易错点

⚠️ 常见错误汇总
  1. 不证明就用贪心:考试时如果题目要求"证明贪心正确性",不能只写代码。要说明"为什么局部最优能导致全局最优"。
  2. 01背包用贪心:01背包不能用贪心!必须用DP。只有分数背包(物品可以部分取)才能贪心。
  3. 排序方式搞错:区间调度按结束时间排,不是按开始时间!按开始时间排贪心不一定正确。
  4. 漏考虑边界:第一个区间不需要判断和前一个是否重叠(因为last_end初始化为-1)。
  5. 贪心和DP搞混:如果题目说"每个物品只能选一次"且不能分割,大概率是DP不是贪心。

🎯 练习建议

📝 循序渐进练习路径
  1. 先理解贪心的本质:在纸上分析"为什么这样贪是对的"。
  2. 做经典题目:
    • P1803 凌乱的yyy(洛谷区间调度入门)
    • P1020 导弹拦截(贪心+二分)
    • P1090 合并果子(贪心+优先队列)
  3. 对比练习:拿同一个问题,分别用贪心和DP做,看什么时候贪心正确什么时候不正确。
  4. 学会证明:尝试用反证法证明贪心策略的正确性。