题解:洛谷 P2440 木材加工

【题目来源】

洛谷:P2440 木材加工 - 洛谷

【题目描述】

木材厂有 \(n\) 根原木,现在想把这些木头切割成 \(k\) 段长度\(l\) 的小段木头(木头有可能有剩余)。

当然,我们希望得到的小段木头越长越好,请求出 \(l\) 的最大值。

木头长度的单位是 \(cm\),原木的长度都是正整数,我们要求切割得到的小段木头的长度也是正整数。

例如有两根原木长度分别为 \(11\)\(21\),要求切割成等长的 \(6\) 段,很明显能切割出来的小段木头长度最长为 \(5\)

【输入】

第一行是两个正整数 \(n,k\),分别表示原木的数量,需要得到的小段的数量。

接下来 \(n\) 行,每行一个正整数 \(L_i\),表示一根原木的长度。

【输出】

仅一行,即 \(l\) 的最大值。

如果连 \(1cm\) 长的小段都切不出来,输出 0

【输入样例】

3 7
232
124
456

【输出样例】

114

【核心思想】

  1. 问题分析:给定 \(n\) 根原木,每根长度为 \(L_i\),需要切割出至少 \(k\) 段长度均为 \(l\) 的小段。目标是求满足条件的最大正整数 \(l\)。若 \(l = 1\) 都无法满足,输出 \(0\)。这是一个整数二分问题,关键在于判断某个切割长度 \(l\) 是否可行(即能否得到 \(\geq k\) 段)。

  2. 算法选择

    • 整数二分(Binary Search on Answer):在答案域 \([1, \max(L_i)]\) 上进行二分查找,将"求最大值"转化为"判定可行性"
    • 可行性判定:给定切割长度 \(mid\),计算所有原木能切出的段数总和 \(\sum_{i=1}^{n} \lfloor L_i / mid \rfloor\),若总和 \(\geq k\)\(mid\) 可行
  3. 关键步骤

    • 初始化:读取 \(n\)(原木数量)、\(k\)(目标段数)、\(L[1..n]\)(各原木长度)
    • 设定搜索范围:左边界 \(l = 0\),右边界 \(r = 10^8\)(或 \(\max(L_i)\)
    • 二分判定(当 \(l \leq r\)\(r \neq 0\) 时循环):
      • 取中点 \(mid = l + (r - l) / 2\),防止溢出
      • 计算可切割段数:\(tmp = \sum_{i=1}^{n} \lfloor L_i / mid \rfloor\)
      • 判定与调整
        • \(tmp \geq k\)\(mid\) 可行,尝试更大的长度,令 \(l = mid + 1\)
        • \(tmp < k\)\(mid\) 不可行,需要更短的长度,令 \(r = mid - 1\)
    • 输出答案:循环结束时 \(r\) 即为满足条件的最大长度(若始终不可行则 \(r = 0\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \log \max(L_i))\),二分约 \(\log 10^8 \approx 27\) 次,每次判定 \(O(n)\)
    • 空间复杂度:\(O(n)\),存储原木长度数组
  5. 整数二分的核心思想

    • 答案域单调性:切割长度 \(l\) 越大,能得到的段数越少,具有单调递减性,满足二分条件
    • 判定与搜索分离:不直接求解,而是构造判定函数 \(check(mid)\) 判断可行性,将最优化问题转化为判定问题
    • 边界处理技巧:使用 \(l = mid + 1\)\(r = mid - 1\) 避免死循环,最终答案取 \(r\)(最后一个可行的值)
    • 适用场景:求满足条件的最大值/最小值,且条件具有单调性的最优化问题

【解题思路】

【算法标签】

普及 #整数二分

【代码详解】

#include <bits/stdc++.h>
using namespace std;

// 全局变量:
// n: 绳子数量
// k: 需要切割的段数
// a[100005]: 存储每根绳子的长度
int n, k, a[100005];

int main()
{
    // 输入绳子数量n和需要切割的段数k
    cin >> n >> k;

    // 输入每根绳子的长度
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }

    // 初始化二分查找的左右边界
    int l = 0, r = 1e8;

    // 二分查找最大可能的切割长度
    while (l <= r && r != 0)
    {
        int tmp = 0;  // 记录当前切割长度下能得到的段数
        int mid = l + (r - l) / 2;  // 计算中间值,防止溢出

        // 计算所有绳子按mid长度切割能得到的段数总和
        for (int i = 0; i < n; i++)
        {
            tmp += a[i] / mid;
        }

        // 根据计算结果调整搜索范围
        if (tmp >= k)
        {
            l = mid + 1;  // 可以切割更长,向右搜索
        }
        else if (tmp < k)
        {
            r = mid - 1;  // 需要切割更短,向左搜索
        }
    }

    // 输出最终确定的最大切割长度
    cout << r << endl;

    return 0;
}

【运行结果】

3 7
232
124
456
114
posted @ 2026-08-10 12:28  团爸讲算法  阅读(36)  评论(0)    收藏  举报