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

专题4 · 贪心(二)

贪心(一)讲的是「排序 + 取最值」,本讲升级到区间类贪心:
把二维问题降维成一维、把冲突关系建模成区间选点——两道真题带你掌握「问题转化」这一关键能力。

P1159 喷水装置 P2207 照相 [USACO13OPEN] 降维 · 区间选点
目标

本讲你要带走的三件事

① 会降维

把「二维圆覆盖矩形」用勾股定理压成一维线段覆盖,有效覆盖长度 = 2√(r²−1)。

② 会建模

把「冲突对不能同框」翻译成「区间内必须放一个断点」,即区间选点问题。

③ 会选点

区间选点的固定贪心:按右端点升序,取未命中区间的右端点。

知识地图(承接专题3)

专题3

排序贪心:优惠券 / 平均分配

本讲①

区间覆盖:喷水装置

本讲②

区间选点:照相

共性

排序 → 取当前最优 → 累加

区间类贪心的口诀:覆盖问题按「能延伸多远」排,选点问题按「右端点」排。先想清楚是「覆盖」还是「选点」,再动手。
回顾

贪心四步法(沿用专题3)

承上:还是四步法,但多了两类题型——区间覆盖 与 分段承上:四步法排序 → 策略 → 执行 → 证明证明那一步依然不能跳新题型① 区间覆盖给一堆区间,选最少的盖住整条线新题型② 分段把序列切成几段使段数最少两类的共同套路:排序 + 能接上就接上,接不上就断开。

1分解子问题:把全局目标拆成一次次「选一个」。

2确定贪心策略:按什么键排序、每次取哪个。

3求子问题最优:实现「取当前最优」这一步。

4堆叠全局最优:累加 / 拼接成答案。

🆕 本讲新增两类题型

  • 区间覆盖:用最少的区间盖满一条线段(喷水装置)。策略:按覆盖长度降序,每次取能延伸最远的。
  • 区间选点:选最少的点,使每个区间至少含一个点(照相)。策略:按右端点升序,取未命中区间的右端点。
两者都靠「排序 + 取当前最优」,但排序键不同——选错键就全错。
3D🎲 降维:把圆变成区间再贪心🖱 拖拽旋转 · 双击复位
为什么用 3D:「降维」是这一讲最值钱的思想。立体的圆喷到地面变成一段区间,学生亲眼看到难题是怎么被降维打击的。
例题1

P1159 · 喷水装置

📖 题意

草坪 长 20 米、宽 2 米。在横中心线上放喷水装置,每个装置湿润以它为中心、半径 R_i 的圆。装置位置可任意选择,问全部湿润最少要几个。

关键①降维:草坪半宽 = 1。半径 r 的圆在长度方向的有效覆盖长度 = 2√(r²−1)(勾股定理)。
关键②排除:r ≤ 1 的装置有效覆盖 ≤ 0,无法湿润草坪,直接丢弃。
策略按有效覆盖长度降序排序,从大到小累加,直到总长 ≥ 20。
样例5 个:2 3.2 4 4.5 6 → 答案 2(11.83 + 8.77 ≥ 20)。
例题1

喷水装置 · 代码走读

#include <iostream>
#include <vector>
#include <cmath>
#include <algorithm>
using namespace std;
int main(){
  int n; cin>>n;
  vector<double> len;
  for(int i=0;i<n;i++){
    double r; cin>>r;
    if(r<=1.0) continue;          // ① 排除无效装置
    len.push_back(2.0*sqrt(r*r-1.0)); // ② 降维
  }
  sort(len.rbegin(),len.rend());      // ③ 覆盖长度降序
  const double L=20.0;
  double sum=0; int cnt=0;
  for(size_t i=0;i<len.size();i++){
    sum+=len[i]; cnt++;
    if(sum+1e-9>=L) break;        // ④ 已盖满
  }
  cout<<cnt<<"\n";
  return 0;
}

🔍 四步对应

  • 分解:每次选「一个装置」来延长已湿润的长度。
  • 策略:每次取当前能延伸最远的(有效长度最大)。
  • 求子最优:排序后从头取。
  • 堆叠:长度累加,达 20 即停。
⚠️ 浮点比较要留余量:写成 sum >= 20 可能因精度差一点点而多算一个装置;用 sum + 1e-9 >= L 更稳。
例题1

喷水装置 · 累加覆盖模拟器

样例 已湿润 0.00 / 20 m
每一步选覆盖最长的装置,相当于让「已湿润长度」增长最快;用交换论证:把最优解里第一个不同的装置换成更长的那个,已湿润长度只会更长,装置数不会更多。故贪心最优。
例题2

P2207 · 照相 [USACO13OPEN Photo B]

📖 题意

n 头奶牛排成一列编号 1..n。每张照片是连续一段,每头牛至少入镜一次;有 k 对关系不好的牛,不能出现在同一张照片。求最少照片数。

建模拆开冲突对 (a,b)(设 a<b)→ 必须在它们之间放一个断点(照片分界),断点位置需落在区间 [a, b−1]。
转化于是成为区间选点:选尽量少的点,使每个区间 [a, b−1] 至少含一个点。
答案照片数 = 断点数 + 1(k=0 时答案为 1)。
样例n=7:(1,3)(2,4)(5,6) → 区间 [1,2][2,3][5,5] → 断点 2、5 → 答案 3。
⚠️ 输入不保证 a < b,读入后要先交换;a == b 的退化对直接忽略。
例题2

照相 · 代码走读

struct Seg{ long long l,r; };
bool cmp(Seg a,Seg b){
  if(a.r!=b.r) return a.r<b.r;   // 右端点升序
  return a.l<b.l;
}
int main(){
  long long n; int k; cin>>n>>k;
  vector<Seg> segs;
  for(int i=0;i<k;i++){
    long long a,b; cin>>a>>b;
    if(a>b) swap(a,b);            // ① 规范化
    if(a==b) continue;
    segs.push_back({a,b-1});       // ② 断点区间
  }
  sort(segs.begin(),segs.end(),cmp); // ③ 按右端点升序
  long long last=-1; int cuts=0;
  for(size_t i=0;i<segs.size();i++){
    if(segs[i].l>last){            // ④ 尚未命中
      last=segs[i].r; cuts++;      //    取右端点
    }
  }
  cout<<cuts+1<<"\n";           // ⑤ 照片数
  return 0;
}

🔍 为什么取「右端点」

区间选点的贪心:处理按右端点升序排好的区间,若当前区间还没被已选的点命中,就选它的右端点——右端点越靠右,越可能顺便命中后面的区间,所以是最「划算」的选择。

判定「是否已命中」:只要 区间左端点 ≤ 上一个断点,就说明已被覆盖,跳过。
⚠️ n 可达 10⁹,但只用到 a、b 做整数比较,不需要开数组;区间数 ≤ 1000,排序开销极小。
例题2

照相 · 区间选点模拟器

样例
m 个断点把 1..n 切成 m+1 段,每段拍一张照片。所以「最少断点数」求出后,别忘了 +1 才是答案——这是本题最常见的漏算。
收口

两题对比:同样是排序,键不同

两题对照:同一个「排序 + 贪心」骨架,换的是排序键喷水装置建模:圆 → 区间 [x−√(R²−1), x+√(R²−1)]排序键:左端点从小到大策略:能接上就往右延伸,接不上就新开一个照相建模:冲突对 → 每一段的右端点上界排序键:冲突对的右端点策略:能延伸就延伸,撞上冲突就在这里断开共同点:都靠「能接上就接上」;差别只在什么算接得上。

💧 喷水装置(区间覆盖)

  • 目标:用最少的装置盖满 20 米。
  • 排序键:有效覆盖长度降序。
  • 每步:取能延伸最远的。
  • 停止:累计 ≥ 20。

📷 照相(区间选点)

  • 目标:用最少的断点命中所有区间。
  • 排序键:区间右端点升序。
  • 每步:取未命中区间的右端点。
  • 收尾:断点数 + 1。
交换论证模板(两题通用):假设存在一个比贪心更优的解,找到它与贪心的第一个分歧点,把该处的选择换成贪心的选择——覆盖题里换成更长的(只会盖得更多),选点题里换成右端点(只会命中更多区间)。新解不更差,故贪心最优。
检测

易错辨析 & 当堂检测

💡 易错辨析

照相题中,冲突对 (a,b)(a<b)对应的断点区间是?

📝 当堂检测

喷水装置中,半径 r=1.0 的装置应该?

⚠️ 两题都别忘了:喷水装置用 double 并留 1e-9 余量;照相要先把 a,b 交换成 a<b,且答案是「断点数 + 1」。
拿分

一道题 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 道题,各自的四档怎么走

喷水装置 · SPRINKLER

第1档 · 2 分钟
所有 R_i ≤ 1 → 一个都盖不住,直接按无解分支输出

第2档 · 8 分钟
枚举装置的所有子集判断能否盖满,n ≤ 20 时 2ⁿ 稳过

第3档 · 10 分钟
按半径从大到小排序逐个放(贪心雏形,能拿不少分)

第4档 · 15 分钟
转成区间 [x−√(R²−1), x+√(R²−1)],按左端点排序做区间覆盖

照相 · PHOTO

第1档 · 2 分钟
k == 0(没有冲突对)→ 1 张照片就够

第2档 · 8 分钟
枚举所有分段方案 2^(n−1),n ≤ 20 稳过

第3档 · 10 分钟
冲突对全是相邻牛 → 退化成简单的「断点计数」

第4档 · 15 分钟
从左到右贪心:能延伸就延伸,遇到冲突就在这里断开

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

椰程锦囊 · 区间类贪心三步自检

① 是覆盖还是选点?

要「盖满一段」→ 覆盖(按长度降序);要「戳中每个区间」→ 选点(按右端点升序)。

② 排序键对了吗?

这是区间贪心唯一的胜负手。写之前先说清「按什么排、为什么」。

③ 收尾 +1 了吗?

选点题:断点数 + 1 = 段数/照片数。漏了就差 1。

椰程锦囊:考场上遇到区间题,先在草稿纸上画一条线、把区间标上去,用手比一比「该选哪个点」——这个动作能救回大多数排序键写错的题。
① P1159 喷水装置:注意 double 与 1e-9;② P2207 照相:注意 a,b 交换、退化对、答案 +1。两题均已生成 Hydro 题包,可直接导入「椰程信奥当堂测评」。

目录