【洛谷 P2440】 木材加工之二分答案入门详解
一、题目分析
题目大意
木材厂有 n 根原木,要把它们切割成 k 段长度均为 l 的小段木头(木头可以有剩余),我们希望 l 尽可能大,求 l 的最大值(单位:cm,l 必须为正整数)。如果连 1cm 都切不出 k 段,输出 0。
输入输出示例
输入:
3 7
232
124
456
输出:
114
解释:
每根原木分别能切出 232/114=2、124/114=1、456/114=4 段,合计 2+1+4=7 段,刚好满足需求,且无法找到更大的 l。
数据规模
- 1 ≤ n ≤ 1e5
- 1 ≤ k ≤ 1e8
- 1 ≤ Li ≤ 1e8
暴力枚举 l 会超时,需要用二分答案优化到 O(n log(maxL))。
二、解题思路:二分答案
核心思想
题目要求 “满足条件的最大 l”,而如果某个长度 l 能切出 k 段,那么所有小于 l 的长度也一定能切出至少 k 段(比如 l=5 能切出 6 段,那么 l=4 肯定能切出更多段)。这种 “满足条件的解是连续区间,且存在最大解” 的问题,就是典型的二分答案应用场景:
- 确定答案的范围:l 的最小值是 1,最大值是原木的最大长度 maxL。
- 对区间进行二分,每次取中间值 mid,判断:如果所有原木按 mid 切割,得到的段数是否 ≥ k。
- 若满足:说明 mid 是可行解,我们尝试找更大的解,更新左边界 l = mid。
- 若不满足:说明 mid 太大,需要尝试更小的解,更新右边界 r = mid。
- 最终找到的 l 就是满足条件的最大长度。
关键:check 函数
- 函数作用:给定一个长度 q,计算所有原木按 q 切割,总共能得到多少段。
- 实现:遍历所有原木,每根原木能切出 Li / q 段,累加得到总数,判断是否 ≥ k。
- 复杂度:O(n),结合二分的 O(log(maxL)),总复杂度 O(n log(maxL)),完全能通过 n=1e5 的数据。
![在这里插入图片描述]()
三、完整代码解析
#include <iostream>
using namespace std;
const int N = 1e5 + 5;
int n, k;
int a[N];
// check函数:判断长度为q时,能否切出至少k段
bool check(int q) {
int y = 0;
for (int i = 1; i <= n; i++) {
y += a[i] / q;
}
return y >= k;
}
// 二分查找:寻找满足条件的最大l
int find() {
// 注意:l从0开始,避免所有原木都切不出1cm的情况(输出0)
int l = 0, r = 1e8 + 5;
// 二分循环:l+1 < r 是为了避免死循环,最终l和r相邻
while (l + 1 < r) {
int mid = l + r >> 1; // 等价于 (l + r) / 2,位运算效率更高
if (check(mid)) {
// 满足条件,尝试更大的解
l = mid;
} else {
// 不满足条件,尝试更小的解
r = mid;
}
}
return l;
}
int main() {
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
printf("%d", find());
return 0;
}

代码细节说明
- 数组范围:
const int N = 1e5 + 5,确保能存下 n=1e5 根原木的长度。 - 边界处理:l 初始化为 0,是为了处理 “连 1cm 都切不出 k 段” 的情况,此时 check(1) 会返回 false,最终 l 保持为 0,输出正确。
- 二分循环写法:
while (l + 1 < r)是二分答案的常用写法,避免了死循环,且循环结束后 l 就是最大可行解。 - 效率优化:用 scanf/printf 代替 cin/cout,避免大数据量下的输入输出超时。
四、易错点与调试技巧
1. 数据类型溢出
累加段数 y 时,可能超过 int 的范围(比如 n=1e5,每根原木切出 1e4 段,总和是 1e9,超过 int 上限 2e9 左右,部分编译器会出问题)。
优化:可以把 y 定义为 long long,避免溢出。
bool check(int q) {
long long y = 0; // 改为long long
for (int i = 1; i <= n; i++) {
y += a[i] / q;
}
return y >= k;
}
2. 边界条件错误
若 l 初始化为 1,当所有原木长度都小于 k 时,会错误地返回 1,而正确结果应为 0,所以 l 初始为 0 更安全。
3. 二分循环死循环
若写成 while (l < r),且 mid = (l + r + 1) / 2,需要注意更新边界的方式,否则可能陷入死循环。l + 1 < r 的写法更稳定,推荐使用。
五、拓展与同类题目
同类二分答案题目
- 洛谷 P1873 砍树:和本题思路几乎完全一致,求满足条件的最大切割高度。
- 洛谷 P1182 数列分段 Section II:求分段后的最大和的最小值,也是典型的二分答案应用。
二分答案的通用模板
对于 “求满足条件的最大 / 最小值” 的问题,都可以套用这个模板:
// 通用二分答案模板(求最大值)
int solve() {
int l = 下界, r = 上界;
while (l + 1 < r) {
int mid = l + r >> 1;
if (check(mid)) {
l = mid;
} else {
r = mid;
}
}
return l;
}

浙公网安备 33010602011771号