专题8 · 二分(二)· 二分答案
上一讲二分的是「数组里的下标」,这一讲二分的是「答案本身」。
当题目问「最大能是多少 / 最小能是多少」时,直接求很难,但验证一个值行不行往往很简单——
这正是二分答案的用武之地。
把「求最优」变成「判定可行」
🔍 上一讲:二分查找
在一个已有的有序数组里找位置。
候选集合是显式的(下标 1…n)。
🎯 这一讲:二分答案
在答案的可能取值里找最优的那个。
候选集合是隐式的(一个数值区间)。
写之前先想清楚这三件事
① 答案范围 [l, r]
最小可能值与最大可能值。
技巧:下界常取「单元素极值」,上界常取「总和」。
② 单调性
答案越大 → 越容易(或越难)满足?
必须同向变化,否则不能二分。
例:H 越大,木料越少(越难达标)。
③ check(x) 函数
给定候选答案 x,判定是否可行。
一般是 O(n) 的扫描或贪心。
最大值最小化 / 最小值最大化
📉 最大值最小化
问法:「分成 m 段,让最大的那段尽量小」。
二分方向:候选 X 越大 → 需要的段数越少 → 越容易满足。
所以 check(X) = 段数 ≤ m,求最小满足值(模板①)。
📈 最小值最大化
问法:「锯片尽量高,同时保证木料够」。
二分方向:高度 H 越大 → 木料越少 → 越难满足。
所以 check(H) = 木料 ≥ m,求最大满足值(模板②)。
记住上一讲的判据:是
l = mid 还是 r = mid,决定 mid 要不要 +1。砍树 · 二分答案模拟器
伐木工人砍树:最典型的二分答案
需要 m 米木料,锯片高度 H 越高、砍到的木料越少。问 H 最高能设多少。
① 范围
H ∈ [0, 最大树高]。样例中最大树高 46。
② 单调性
H ↑ → 木料 ↓。
单调递减的可达性。
③ check(H)
累加 max(0, aᵢ − H),判断 ≥ m。
砍树 · 代码走读
typedef long long ll; ll mx = 0; // 最大树高,作为上界 for(i=0;i<n;i++){ cin>>a[i]; if(a[i]>mx) mx=a[i]; } ll l=0, r=mx, ans=0; while(l <= r){ ll mid = (l+r)/2; ll wood = 0; for(i=0;i<n;i++) if(a[i] > mid) wood += a[i] - mid; if(wood >= m){ ans = mid; l = mid+1; } // 够→还能更高 else { r = mid-1; } // 不够→必须降低 } cout << ans << "\n";
if(a[i] > mid) 不能省,否则低于 H 的树会贡献负数木料。数列分段:最大值最小化
把 n 个数分成 m 段,让「最大的那段的和」尽量小。
① 范围
下界 = 最大元素(一段至少装下它);
上界 = 总和(全放一段)。
② 单调性
X ↑ → 每段能装更多 → 需要段数 ↓。
X 太大时只需 1 段,太小时要 n 段。
③ check(X)
贪心:从左往右装,装不下就新开一段。
统计段数,判断 ≤ m。
数列分段 · 代码走读
ll l = 0, r = 0; for(i=1;i<=n;i++){ cin>>a[i]; if(a[i]>l) l=a[i]; r+=a[i]; } // l = 单元素最大值(下界) r = 总和(上界) while(l < r){ ll mid = (l+r)/2; int cnt = 1; ll cur = 0; // ★ cnt 从 1 开始 for(i=1;i<=n;i++){ if(cur + a[i] <= mid) cur += a[i]; else { cnt++; cur = a[i]; } } if(cnt <= m) r = mid; // 段数够少→上限还能更小 else l = mid+1; } cout << l << "\n";
cnt 初值是 1 不是 0(至少有一段),写成 0 会让段数少算一段;② 下界必须取最大元素,取 0 会让 mid 过小、贪心分段时单元素都装不下,导致 cnt 虚高;③ cur 与答案要用 long long(总和可达 10⁹,虽 int 勉强够但边界危险)。数列分段 · check(X) 贪心演示
二分答案高频易错辨析
① 砍树中,若 check(H) 表示「木料 ≥ m」,那么 H 变大时?
② 数列分段的 check 里,段数计数器 cnt 初值应该是?
③ 数列分段的二分下界应取?
一道题 100 分是分档给的——不会正解也要先拿保底
复赛一道题的 100 分由十几到二十几个测试点组成, 前面几档专门为「没想出正解的人」准备。四道题各拿 30~40 分, 加起来往往就是一等奖和二等奖的分界。
① 特判档:先别想算法,看数据范围表
题目给的数据范围表就是出题人给你的送分清单。看到「n = 1」「只有一组数据」这类一行,
就先写个 if 直接输出答案。2 分钟换 5~10 分,全场最划算。
② 暴力档:按题意最直白地写一遍
多重循环、DFS 全枚举、朴素 O(n³)……不要优化,只要保证最小那档全对。 它既是保底分,又是后面对拍的标尺——没有它你无法证明正解是对的。
③ 特殊性质档:题目里那句「若……」
常见的送分性质:数据已经有序、所有值完全相同、规模小到可以 O(n²)、 只出现一种类型。题目写出来就是让你拿的,专门写一份即可。
④ 正解档:思路定了,就赢了八成
考场上的时间几乎都花在想思路上,不是写代码。写完先跑样例, 再用第 ② 档的暴力版造小数据对拍——这是唯一能证明你思路对的办法。
本讲 2 道题,各自的四档怎么走
伐木工人砍树 · TREE
第1档 · 2 分钟M == 0 → 答案就是最高那棵树;M > 总长 → 0
第2档 · 8 分钟
从最高树往下逐米试,找到第一个够用的高度,maxH 小时稳过
第3档 · 10 分钟
所有树高相同 → 直接算 h − M/n
第4档 · 15 分钟
二分锯高 H,check = Σ max(0, h_i − H) ≥ M,求最大 H
数列分段 Section II · SEGMENT
第1档 · 2 分钟M == 1 → 答案 = 总和;M == n → 答案 = 最大值
第2档 · 8 分钟
枚举答案 x 从小到大,check 用贪心分段,总和小时稳过
第3档 · 10 分钟
所有数相同 → 答案 = ceil(n/M) × a
第4档 · 15 分钟
二分答案 x,左界 max(a)、右界 sum(a),check 贪心分段数 ≤ M
0–5 分钟 读题 + 圈出数据范围表里最小的那几档 → 5–15 分钟 写完第 1、2 档并先交一次(保住 20~30 分)→ 15–40 分钟 冲第 3、4 档 → 40–50 分钟 造小数据对拍、检查
freopen 与文件名。本讲知识树与自检清单
🌳 知识树
识别:问「最大/最小能是多少」+ 直接求很难。
三要素:范围、单调性、check。
两问法:最大值最小化(模板①)、最小值最大化(模板②)。
复杂度:O(n log V)。
✅ 考场 N 步自检
- 单调性方向想清楚了吗?用两个 x 手验过吗?
- 范围下界/上界取对了吗?(极值 / 总和)
- check 是 O(n) 吗?有没有更好的贪心?
- 该用模板①还是②?看 check 成立时收缩哪边。
- 需要 long long 吗?(n=10⁵、值 10⁹ 时基本都要)
- 计数器初值对了吗?(分段题 cnt=1)