【洛谷 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 肯定能切出更多段)。这种 “满足条件的解是连续区间,且存在最大解” 的问题,就是典型的二分答案应用场景:

  1. 确定答案的范围:l 的最小值是 1,最大值是原木的最大长度 maxL。
  2. 对区间进行二分,每次取中间值 mid,判断:如果所有原木按 mid 切割,得到的段数是否 ≥ k。
    • 若满足:说明 mid 是可行解,我们尝试找更大的解,更新左边界 l = mid。
    • 若不满足:说明 mid 太大,需要尝试更小的解,更新右边界 r = mid。
  3. 最终找到的 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;
}

在这里插入图片描述

代码细节说明

  1. 数组范围const int N = 1e5 + 5,确保能存下 n=1e5 根原木的长度。
  2. 边界处理:l 初始化为 0,是为了处理 “连 1cm 都切不出 k 段” 的情况,此时 check(1) 会返回 false,最终 l 保持为 0,输出正确。
  3. 二分循环写法while (l + 1 < r) 是二分答案的常用写法,避免了死循环,且循环结束后 l 就是最大可行解。
  4. 效率优化:用 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 的写法更稳定,推荐使用。

五、拓展与同类题目

同类二分答案题目

  1. 洛谷 P1873 砍树:和本题思路几乎完全一致,求满足条件的最大切割高度。
  2. 洛谷 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;
}

posted on 2026-06-14 21:50  5iCode  阅读(2)  评论(0)    收藏  举报

导航