OneCoder Avatar
OneCodercoderli.com · 962 篇博文
五级

C++ 算法考级专栏

真题分析、矩阵探测、递归回溯与基础语法

🎨 视觉封面

【GESP/CSP练习】GESP五级 / CSP-J 题解:luogu-P2440 木材加工

📅 2026-09-21·✍️ OneCoder·计算中...·⏱️ 12 分钟
#洛谷#C++#二分答案#单调性#贪心#GESP五级#CSP-J

洛谷经典算法题 P2440「木材加工」,是算法竞赛与等级考试中**二分答案(Binary Search on Answer)**思想的极具代表性的入门与进阶典例。本题标准收录于 CCF GESP 五级考纲(二分查找与二分答案核心考点)以及 CSP-J 普及组算法题单。题目核心考查如何将“求满足条件的最大值”这一最优化目标,转化为“给定长度检验是否可行”的判定性问题(Decision Problem),利用切分段数随单段长度递增而严格单调不增的数学性质,在 O(nlog(maxLi))\mathcal{O}(n \log (\max L_i)) 的高效时间复杂度内锁定最优解。题目难度⭐⭐⭐☆☆,洛谷难度评级为普及/提高-

luogu-P2440 木材加工

🔗 洛谷原题传送门luogu-P2440 木材加工

题目描述

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

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

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

例如有两根原木长度分别为 11112121,要求切割成等长的 66 段,很明显能切割出来的小段木头长度最长为 55

输入格式

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

接下来 nn 行,每行一个正整数 LiL_i,表示一根原木的长度。

输出格式

仅一行,即 ll 的最大值。

如果连 1cm\text{1cm} 长的小段都切不出来,输出 0。\n\n### 输入输出样例

输入 #1

Code 4 行
3 7
232
124
456

输出 #1

Code 1 行
114

说明/提示

数据规模与约定

对于 100%100\% 的数据,有 1n1051\le n\le 10^51k1081\le k\le 10^81Li108(i[1,n])1\le L_i\le 10^8(i\in[1,n])


题目深度剖析

1. 问题建模与单调性分析

题目给定 nn 根原木,长度分别为 L1,L2,,LnL_1, L_2, \dots, L_n,要求切出至少 kk 段长度均为 ll 的小木段,并最大化目标长度 ll

若我们设函数 f(l)f(l) 表示:当切出的小木段长度为 ll 时,所有原木最多能切出的总段数。 由于每根原木只能独立切分(木料无法拼接),长度为 LiL_i 的原木能切出 Lil\lfloor \frac{L_i}{l} \rfloor 段,因此: f(l)=i=1nLilf(l) = \sum_{i=1}^{n} \left\lfloor \frac{L_i}{l} \right\rfloor

观察函数 f(l)f(l) 的数学性质:

  1. 单调不增性:随着小木段长度 ll 的不断增大,每一项 Lil\lfloor \frac{L_i}{l} \rfloor 均单调不增,其总和 f(l)f(l) 必然关于 ll 单调递减(或保持不增)。
  2. 可行性判定:若某个长度 ll 能够切出不少于 kk 段(即 f(l)kf(l) \ge k),则对于任意小于 ll 的正整数 l<ll' < l,必有 f(l)f(l)kf(l') \ge f(l) \ge k 同样成立;反之,若长度 ll 无法切出 kk 段(f(l)<kf(l) < k),则任意大于 ll 的长度也绝不可能切出 kk 段。

这种在解空间内呈现出绝对“单调分界”的特性,正是**二分答案(Binary Search on Answer)**的标准应用场景!

2. 二分答案搜索区间设计

我们需要确定答案 ll 的上下界:

  • 下界(Left Bound):题目要求小木段长度为正整数,因此最小有效长度为 11。即初始 left=1left = 1
  • 上界(Right Bound):切出的小木段不可能比所有原木中最长的一根还要长,因此上界可直接取 right=max1in(Li)108right = \max_{1 \le i \le n} (L_i) \le 10^8

二分转移流程

  • 取区间中点 mid=left+rightleft2mid = left + \lfloor \frac{right - left}{2} \rfloor
  • 遍历所有原木,计算 count=i=1nLimidcount = \sum_{i=1}^n \lfloor \frac{L_i}{mid} \rfloor
  • countkcount \ge k:说明当前长度 midmid 是合法的,但可能还存在更长的合法长度,我们记录当前最优答案 ans=midans = mid,并尝试向右半区间探索更优解(令 left=mid+1left = mid + 1);
  • count<kcount < k:说明当前长度 midmid 偏大,导致切出的段数不足 kk,该长度不可行,向左半区间压缩查找范围(令 right=mid1right = mid - 1)。

left>rightleft > right 时,二分终止,ansans 即为所求的 ll 的最大可能值。

3. 边界特判与整型溢出避坑

在 GESP 五级和 CSP-J 考场上,该题有两大极为致命的失分点:

  1. 总段数累加溢出 32 位整型(“不开 long long 见祖宗”)

    • 题目中 n105n \le 10^5Li108L_i \le 10^8
    • 在二分初期,当 midmid 较小(例如 mid=1mid = 1)时,每根原木切出的段数可达 10810^8
    • nn 根原木的总段数理论最大值可达 n×Li=105×108=1013n \times L_i = 10^5 \times 10^8 = 10^{13}
    • 32 位有符号整型 int 的最大上限约为 2.14×1092.14 \times 10^9。若使用 int 存储累加变量 count,将发生严重溢出导致判定错误!
    • 应对策略:累加段数 count、原木总长 total_len 以及目标需求 kk 必须使用 64 位整型 long long。同时在 check 函数中,一旦 count >= k 即可提前 return true 实现贪心剪枝。
  2. 全长不足 kk 时的 00 特判

    • 题目明确要求:“如果连 1cm1\text{cm} 长的小段都切不出来,输出 0”;
    • 若原木总长之和 Li<k\sum L_i < k,即便每段只取 1cm1\text{cm} 也无法满足要求;
    • 我们可以在二分前直接统计总长 Li\sum L_i,若小于 kk 直接输出 00 退出程序;即使不预先特判,若我们将初始 ans=0ans = 0,当 mid=1mid = 1 判定失败时,二分区间会直接收缩至 right=0right = 0,最终输出 ans=0ans = 0,逻辑天然闭环。

4. 复杂度分析

  • 时间复杂度
    • 二分区间长度为 maxLi108\max L_i \le 10^8
    • 二分查找循环次数为 log2(108)27\lceil \log_2(10^8) \rceil \approx 27 次;
    • 每次判定需要遍历一次长度为 nn 的数组,耗时 O(n)\mathcal{O}(n),且配合 count >= k 提前退出常数极小;
    • 总体时间复杂度为 O(nlog(maxLi))\mathcal{O}(n \log(\max L_i))。代入 n=105n = 10^5,总基本操作次数约 2.7×1062.7 \times 10^6 次,在 1.0s1.0\text{s} 时限内仅耗时约 10ms10\text{ms},极其充裕。
  • 空间复杂度
    • 仅需一个全局静态数组存储 nn 根原木长度,空间复杂度为 O(n)\mathcal{O}(n),占用内存约 400KB400\text{KB},远低于题目限制。

完整参考代码 (C++11)

C++ 76 行
/**
 * Problem: luogu-P2440 木材加工
 * Algorithm: 二分答案 (Binary Search on Answer) / 单调性判定
 * Standard: C++11 (CCF GESP 官方大纲规范)
 * Author: OneCoder
 */

#include <iostream>
#include <algorithm>

using namespace std;

// 数据规模:n <= 10^5, 原木长度 L_i <= 10^8
const int MAXN = 100005;
int a[MAXN];

// check 函数:检验是否能够切割出至少 k 段长度为 len 的小木头
// 单调性核心:len 越小,切出的小段越多;len 越大,切出的小段越少
bool check(int len, int n, long long k) {
    long long count = 0;
    for (int i = 0; i < n; ++i) {
        // 每根原木长度为 a[i],最多可切出 a[i] / len 段长度为 len 的小木头
        count += a[i] / len;
        // 剪枝:一旦累计段数达到或超过目标 k,说明该长度可行,直接返回 true
        if (count >= k) {
            return true;
        }
    }
    return count >= k;
}

int main() {
    int n;
    long long k;
    cin >> n >> k;

    int max_len = 0;
    long long total_len = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        if (a[i] > max_len) {
            max_len = a[i];
        }
        total_len += a[i];
    }

    // 边界特判:若所有原木总长度累加仍小于目标段数 k,
    // 则即便每段长度取最小正整数 1cm,也无法切出 k 段,直接输出 0
    if (total_len < k) {
        cout << 0 << endl;
        return 0;
    }

    // 二分答案:小段长度 l 的取值范围为 [1, max_len]
    int left = 1;
    int right = max_len;
    int ans = 0;

    while (left <= right) {
        int mid = left + (right - left) / 2;
        // 判定当前长度 mid 是否满足要求
        if (check(mid, n, k)) {
            // 如果长度为 mid 可行,记录该可行解,并尝试寻找更长的小段(向右半区间搜索)
            ans = mid;
            left = mid + 1;
        } else {
            // 如果长度为 mid 无法切出 k 段,说明太长了,向左半区间压缩
            right = mid - 1;
        }
    }

    // 输出所能得到的小段木头的最大长度 l
    cout << ans << endl;

    return 0;
}
💡 OneCoder 资源指引

所有代码开源上传至 GitHub:yummy-code 仓库 · GESP 专题站:GESP WIKI

🤝 技术交流与答疑

欢迎加入:C++ GESP/CSP 考级答疑群(688906745)Java/Python交流群(982860385),点击可直接加群。

📚

猜你想读 · 相关文章推荐

GESP 编程与算法 · 五级⏱️ 9 分钟

【GESP真题】GESP五级 / CSP-J 题解:luogu-P17456 [GESP202609 五级] 饮品调制

CCF GESP 2026年9月认证(第十五次认证)C++ 五级试题,洛谷 P17456。本题严格遵循 CCF GESP 官方大纲规范,重点考察贪心算法与平均值平衡杠杆原理。题目逻辑严密,模型典型,是深入理解与掌握信奥核心考点的经典范例。

阅读全文 →
GESP 编程与算法 · 五级⏱️ 10 分钟

【GESP/CSP练习】GESP五级 / CSP-J 题解:luogu-P1075 [NOIP2012 普及组] 质因数分解

NOIP 2012 普及组第一题,洛谷 P1075。本题是算法竞赛与等级考试中极为经典的初等数论与质因数分解启蒙代表作,标准收录于 CCF GESP 五级考纲(初等数论:质数判定、因数分解与欧几里得算法考点)以及 CSP-J 普及组数论基础必做题单。题目核心考察如何利用正整数唯一分解定理与因数成对对称分布规律,避开低效...

阅读全文 →
GESP 编程与算法 · 五级⏱️ 14 分钟

【GESP/CSP练习】GESP五级 / CSP-J 题解:luogu-P1029 [NOIP2001 普及组] 最大公约数和最小公倍数问题

NOIP 2001 普及组第二题,洛谷 P1029。本题是算法竞赛与等级考试中极为经典的初等数论、欧几里得算法与因数枚举代表作,标准收录于 CCF GESP 五级考纲(初等数论:最大公约数 gcd\gcd、最小公倍数 lcm\operatorname{lcm}、质因数分解与欧几里得算法考点)以及 CSP-J 普及组数论必做...

阅读全文 →
OneCoder

OneCoder (lihongzheshuai)

一个中年人的自留地,记录学习 C++、GESP/NOI、Java、Python 与算法架构的心得体会。本站唯一网址:coderli.com

读者讨论与留言

0 条讨论
✨ 支持点击留言直接回复 · Markdown 引用格式
💬 还没有读者留言,快来成为第一个讨论者吧!