椰程信奥·互动课件 专题3·贪心(一)
10:00 1 / 15
🧭 椰程信奥 · CSP复赛专题系列

专题3 · 贪心(一)

贪心不是“瞎贪”,而是每一步都取当前最优、并相信它能堆出全局最优。
本讲带你建立「四步解题法」,并学会用反证 / 交换论证证明贪心真的对,最后用两道真题练手。

ABC246C 优惠券 GESP202503 五级 T1 平均分配 四步法 + 交换论证
目标

本讲你要带走的三件事

本讲路线:先知道贪心「是什么」,再练「怎么证」① 是什么每步选当前最好的② 四步法排序·策略执行·证明③ 怎么证交换论证找反例④ 练两题优惠券平均分配学完的判据:拿到一道新题,能在 3 分钟内说出「排序键是什么」。

① 认得贪心

看到「选局部最优能拼出全局最优」的题面,能判断它大概率是贪心,并说出使用前提。

② 会写四步

用分解→定策略→求子最优→堆叠全局的固定流程落地代码,避免想到哪写到哪。

③ 敢证明

用反证 / 交换论证说明策略为什么不会更差,把「我觉得对」变成「能证明对」。

知识地图

本质

局部最优 → 全局最优

前提

无后效性、最优子结构

方法

排序 / 优先队列 / 选择

证明

反证法、交换论证

贪心题 80% 的工作量在「排序排什么、按什么顺序选」——把这一句想透,代码通常很短。
本质

贪心:每一步都选「当前看起来最好」的

核心逻辑——选择每一阶段的局部最优,从而希望得到全局最优。前提是「局部最优一定会导致全局最优」。

🎯 经典例子:取十张钞票

桌上有十张钞票(100 / 50 / 20 / 10 / 5 / 1 元各若干),让你取一张使金额最大。

任何人都会选面值最大的那张——因为「取最大」这一步的局部最优,不会让之后的选择变差。这就是贪心。

✅ 这里贪心成立:大额钞票总是优于小额,且无后效性。

⚠️ 什么时候贪心会翻车

  • 选了当前最优,却堵死了后面更优的组合(有后效性)。
  • 「局部最优」和「全局最优」目标不一致。
  • 例:找零用最少张数——若面额是 1/3/4,付 6 元,贪心取 4+1+1(3 张)不如 3+3(2 张)。
所以:大胆假设、小心求证。写完贪心一定要证明或至少举反例检验。

🖼️ 看图:贪心为什么会翻车(付 6 元,面额 1 / 3 / 4)

目标:凑出 6 元,钞票面额只有 1、3、4,要求张数最少 ① 贪心:每次都拿眼前最大的 4 + 1 + 1 = 6 元 共 3 张 ✗ 不是最少 拿完 4 之后只剩 2, 只能用 1+1 补 → 被"堵死"了 ② 正确答案:退一步,反而更少 3 + 3 = 6 元 共 2 张 ✓ 更少 第一张不拿 4 而拿 3,剩下的 3 正好 又是一张 3 → 只花 2 张 📌 记住:贪心 = 眼前最好;但"眼前最好"有时会堵死后面的路。写完一定要举反例试一试。
3D🎲 贪心 = 每一步都挑当前最高的🖱 拖拽旋转 · 双击复位
为什么用 3D:贪心的争议点永远是「凭什么局部最优就是全局最优」。用高低不同的立体柱子做选择,学生能直接看到只看眼前会舍掉什么。
流程

贪心四步法(通用脚手架)

贪心四步:最难的不是第 3 步「选」,而是第 4 步「证明」1 · 排序先按某个键排键选错就全错2 · 定义策略每次拿什么一句话说清3 · 执行从头扫一遍注意边界4 · 证明交换论证否则是假贪心第 4 步不能跳:想不出反例 ≠ 正确。至少要能做一次交换论证。

1分解子问题:把「全局最优」拆成一系列「每步选一个」的决定。

2确定贪心策略:这一步按什么规则选?常见是「按某个值排序后依次取」。

3求子问题最优解:实现「取当前最优」这一步(排序 / 优先队列)。

4堆叠全局最优:把每步结果累加 / 拼接,得到答案;注意数据类型与边界。

🧪 两种证明思路

  • 数学归纳法:假设前 k 步最优,证第 k+1 步选局部最优后仍最优。
  • 反证法 / 交换论证:假设存在一个更优解,把其中一步换成我们的贪心选择,证明不会变差(反而更好或等于),从而推翻「更优解」。
交换论证最常用:把「最优解里的某次选择」和「贪心选择」交换,总价值不降。
证明

用「交换论证」说服自己

交换论证:把「贪心选的」和「别人选的」换一下,看会不会更差贪心解:A 在 B 前…AB…任意解:B 在 A 前…BA…⇄交换之后:如果不更差 → 贪心解不比任意解差 → 贪心正确。如果交换后可能更差,那这个贪心就是假的,必须换 DP 或搜索。

套路(背下来就能套)

第 1 步:写出贪心策略 G(比如「每次选差价最大的物品给小 B」)。

第 2 步:假设存在一个最优解 O 与 G 的第一次分歧发生在物品 i。

第 3 步:在 O 中把「给小 C 的物品 i」换成「给小 B(差价更大)」,总收入只增不减。

第 4 步:反复交换,O 被改成 G 且不更差 → G 也是最优解。得证。

至少做两件事:① 用样例手算验证策略;② 主动构造一个反例(如把排序键换掉)看是否崩。崩了就说明贪心不成立,改用 DP / 搜索。
题型

常见题型与应对

🧩 基础贪心

  • 区间问题:按结束时间排序(活动选择),或按开始时间排序。
  • 分配问题:按对象属性排序后依次分配(本讲例题 2)。
  • 排序问题:选最小 / 最大元素(本讲例题 1 优惠券)。

🔗 贪心 + 其他

  • 贪心 + 枚举:先排序,再在有序结构上枚举。
  • 贪心 + 动态规划:贪心定顺序,DP 在顺序上转移。
  • 贪心 + 模拟:按贪心规则一步步执行(优惠券用券过程)。
记住:排序是贪心的发动机。看到「最优分配 / 最少代价」,先想能按什么键排序。
例题1

ABC246C · 优惠券(AT_abc246_c)

📖 题意

有 N 件商品,第 i 件价 A_i 日元。你有 K 张优惠券,每张券可用于任一商品,同一商品可用任意张。对某商品用 k 张券,它的最终价变为 max{A_i − k·X, 0}。

求买下所有商品所需最小总金额。
约束:1 ≤ N ≤ 2×10⁵,1 ≤ K, X ≤ 10⁹,1 ≤ A_i ≤ 10⁹。

策略券永远该用在当前最贵的商品上——每用一张最多省 X 日元,直到它降到 0。
样例5 4 7 | 8 3 10 5 13 → 答案 12(把 13→0、10→3、8→1,得 0+3+1+3+5=12)。
例题1

优惠券 · 代码走读

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
int main(){
  ll n,k,x; cin>>n>>k>>x;
  vector<ll> a(n); ll sum=0;
  for(int i=0;i<n;i++){ cin>>a[i]; sum+=a[i]; }
  ll ret=0;
  for(int i=0;i<n;i++){ ret+=a[i]/x; a[i]%=x; }  // ① 整 X 减
  sort(a.begin(),a.end());                            // ② 余数升序
  if(k <= ret){                                     // ③ 券不够
    ll s=0; for(int i=0;i<n;i++) s+=a[i];
    cout << (ret-k)*x + s << "\n";
  } else {                                           // ④ 券够,再清最大余数
    ll s=0; for(int i=0;i<n;i++) s+=a[i];
    for(int i=n-1,u=k-ret; u>0 && i>=0; i--,u--) s-=a[i];
    cout << s << "\n";
  }
  return 0;
}

🔍 四步对应

  • 定策略:券永远给当前最贵商品。
  • 求子最优:每件先整 X 减(省 X),余数归零。
  • 堆叠全局:总省 = ret·X + 清掉的余数。
  • 边界:K 可能极大,用整 X 批量减避免 O(K)。
⚠️ 别写错:若「每件直接减到 0 再处理下一件」,会在「一件已 < X 但还有券」时浪费券,不是最优。优化版先整 X 减、再清余数,才是正解。
例题1

优惠券 · 分步用券模拟器

样例 剩余券:4
这里「每张券给当前最大值」是直觉版(每步最优,结果最优);代码是优化版(整 X 批量减 + 清余数)。两种思路答案一致,但优化版能跑 10⁹ 规模的 K。
例题2

GESP202503 五级 T1 · 平均分配(P11960)

📖 题意

小 A 有 2n 件物品。第 i 件,小 B 出价 b_i、小 C 出价 c_i。规定小 B、小 C 各恰好买走 n 件,求小 A 最大总收入。

约束:1 ≤ n ≤ 10⁵,0 ≤ b_i, c_i ≤ 10⁹。答案可能到 2×10¹⁴,必须用 long long。

策略算每件差价 d_i = b_i − c_i,按 d_i 降序排序,前 n 件给小 B(差价大的更该给 B),其余给小 C。
样例n=3:b=[1,3,5,6,8,10] c=[2,4,6,7,9,11] → 答案 36。
例题2

平均分配 · 代码走读

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
struct Item{ ll b,c,d; };
bool cmp(Item x,Item y){ return x.d > y.d; }   // 差价降序
int main(){
  int n; cin>>n;
  vector<Item> a(2*n);
  for(int i=0;i<2*n;i++) cin>>a[i].b;
  for(int i=0;i<2*n;i++) cin>>a[i].c;
  for(int i=0;i<2*n;i++) a[i].d=a[i].b-a[i].c; // ① 算差价
  sort(a.begin(),a.end(),cmp);                    // ② 排序
  ll ans=0;
  for(int i=0;i<n;i++)  ans+=a[i].b;          // ③ 前 n 件给 B
  for(int i=n;i<2*n;i++) ans+=a[i].c;        // ④ 其余给 C
  cout << ans << "\n";
  return 0;
}
⚠️ 考场高频坑:答案用 int 会溢出(2×10⁵ × 10⁹ ≈ 2×10¹⁴)。全用 long long,否则白丢 30 分钟。
例题2

平均分配 · 差价排序分配模拟器

样例
先假设全卖给 C,总收入 Σc_i;要从 2n 件里挑 n 件改卖给 B。改一件 i,收入变化 = b_i − c_i = d_i。挑 d_i 最大的 n 件,总收入增加最多 → 最优。
拿分

一道题 100 分是分档给的——不会正解也要先拿保底

复赛一道题的 100 分由十几到二十几个测试点组成, 前面几档专门为「没想出正解的人」准备。四道题各拿 30~40 分, 加起来往往就是一等奖和二等奖的分界。

第 1 档 特判 5~10 分 约 2 分钟 第 2 档 暴力 20~30 分 约 8 分钟 第 3 档 特殊性质 40~60 分 约 15 分钟 第 4 档 正解 100 分 约 25 分钟

① 特判档:先别想算法,看数据范围表

题目给的数据范围表就是出题人给你的送分清单。看到「n = 1」「只有一组数据」这类一行, 就先写个 if 直接输出答案。2 分钟换 5~10 分,全场最划算。

② 暴力档:按题意最直白地写一遍

多重循环、DFS 全枚举、朴素 O(n³)……不要优化,只要保证最小那档全对。 它既是保底分,又是后面对拍的标尺——没有它你无法证明正解是对的。

③ 特殊性质档:题目里那句「若……」

常见的送分性质:数据已经有序、所有值完全相同、规模小到可以 O(n²)、 只出现一种类型。题目写出来就是让你拿的,专门写一份即可。

④ 正解档:思路定了,就赢了八成

考场上的时间几乎都花在想思路上,不是写代码。写完先跑样例, 再用第 ② 档的暴力版造小数据对拍——这是唯一能证明你思路对的办法。

考场纪律:每档设时间上限,到点还没调通就 立刻提交当前版本,保住已有分数,再往上冲。最常见的翻车是"正解写了 50 分钟没过,连暴力分都没交上去"。
拿分

本讲 2 道题,各自的四档怎么走

优惠券 · COUPON

第1档 · 2 分钟
K == 0 或 X == 0 → 直接输出 sum(A)

第2档 · 8 分钟
每张券单独找「用了还能减最多」的那件商品,模拟 K 次

第3档 · 10 分钟
所有 A_i 相同 → 逐件用券直到用完或减到 0,可直接算

第4档 · 15 分钟
按 A_i 从大到小排序,每件用 min(剩余券, ceil(A_i/X)) 张

平均分配 · AVG

第1档 · 2 分钟
n == 1 → 取 max(b₁+c₂, c₁+b₂)

第2档 · 8 分钟
枚举所有 C(2n, n) 种分配取最大,n ≤ 10 稳过

第3档 · 10 分钟
所有 c_i == 0 → 直接取 b 最大的 n 件给 B

第4档 · 15 分钟
按 (b_i − c_i) 从大到小排序,前 n 件给 B,其余给 C

50 分钟时间盒(一题的节奏):
0–5 分钟 读题 + 圈出数据范围表里最小的那几档 → 5–15 分钟 写完第 1、2 档并先交一次(保住 20~30 分)→ 15–40 分钟 冲第 3、4 档 → 40–50 分钟 造小数据对拍、检查 freopen 与文件名。
课堂上别让学生"从头想正解"。先要求每个人 15 分钟内交出第 1、2 档, 再放开冲正解。这个顺序一换,平均分通常能涨一大截——因为它杜绝了"想了 40 分钟,交上去 0 分"。
收口

易错辨析 & 当堂检测

💡 易错辨析

优惠券题若「每件直接减到 0 再处理下一件」,结果一定最优吗?

📝 当堂检测

平均分配题答案的数据类型应选?

椰程锦囊:贪心三连问——① 按什么排序?② 取前几个 / 怎么分配?③ long long 开了吗?三问答完,题基本就对了。

目录