1 条题解

  • 0
    @ 2025-12-10 12:24:51

    题目理解

    给定 mm 块原木板(长度为 a1,a2,...,ama_1, a_2, ..., a_m)和 nn 块需要的木板(长度为 b1,b2,...,bnb_1, b_2, ..., b_n),通过切割原木板(不产生浪费)得到需要的木板。每块木板(原木板和需要的木板)只能使用一次。求最多能得到多少块需要的木板。

    数据范围m50,n1000m \le 50, n \le 1000


    关键观察

    1. 问题本质

    这是一个典型的二分答案 + 可行性验证问题:

    • 答案具有单调性:如果能得到 kk 块需要的木板,那么一定能得到少于 kk 块的
    • 验证给定 kk 是否可行:能否从原木板中切割出 kk 块需要的木板

    2. 贪心策略

    • 优先切割较短的需要木板(容易满足)
    • 优先使用较短的原木板(避免浪费)
    • 如果当前需要的木板长度与原木板剩余长度相等,直接使用(不产生浪费)

    算法设计

    1. 预处理

    1. 将需要的木板长度 bb 从小到大排序
    2. 将原木板长度 aa 从小到大排序
    3. 计算原木板总长度 Sa=aiS_a = \sum a_i 和需要木板的前缀和 prek=i=1kbipre_k = \sum_{i=1}^k b_i

    2. 二分答案框架

    二分答案 k (0 ≤ k ≤ n):
        - 如果 k=0: 一定可行
        - 如果 pre_k > S_a: 不可能(总长度不足)
        - 否则进行 DFS 验证
    

    3. DFS 验证(可行性检查)

    状态

    • now: 当前要切割的第 now 块需要木板(前 k 块中)
    • last: 上次使用的原木板编号(剪枝优化)
    • waste: 已浪费的总长度(长度过小而无法利用的剩余部分)

    剪枝优化

    1. 长度剪枝:如果当前需要木板长度大于所有原木板剩余长度,则失败
    2. 浪费剪枝:如果 waste > S_a - pre_k,则失败
      • 浪费过多,剩余部分即使全部利用也无法完成
    3. 顺序剪枝
      • 如果当前需要木板长度 = 上一块长度,则从上一块使用的原木板开始尝试
      • 避免重复搜索相同长度的木板
    4. 相等优先:优先使用与原木板剩余长度相等的需要木板(零浪费)
    5. 跳过无效:如果原木板剩余长度 < 当前需要木板长度,则跳过

    4. 算法流程

    1. 排序输入数据
    2. 二分答案 k:
       a. 如果 k=0,返回 true
       b. 如果总长度不足,返回 false
       c. 初始化原木板剩余长度
       d. 进行 DFS 验证
    3. 输出最大可行的 k
    

    算法正确性

    1. 二分答案的正确性

    • 单调性:如果能得到 kk 块木板,那么通过减少切割,一定能得到 k1k-1
    • 可行性验证:DFS 搜索所有可能的切割方案,确保不遗漏

    2. 剪枝的合理性

    1. 浪费剪枝:基于总长度守恒,已浪费长度不能超过允许的最大浪费
    2. 顺序剪枝:相同长度的需要木板具有对称性,固定尝试顺序避免重复
    3. 相等优先:零浪费的切割是最优选择,优先尝试

    3. 搜索完备性

    DFS 遍历所有原木板和所有切割方式,在剪枝优化下仍保证能找到可行解(如果存在)


    复杂度分析

    时间复杂度

    • 排序O(nlogn+mlogm)O(n \log n + m \log m)
    • 二分答案O(logn)O(\log n) 次迭代
    • 每次验证:DFS 搜索,最坏情况 O(mk)O(m^k),但通过剪枝大幅优化
      • 实际运行效率:m50,n1000m \le 50, n \le 1000 下可接受

    空间复杂度

    • 存储原木板和需要木板:O(m+n)O(m + n)
    • DFS 递归栈:O(k)O(n)O(k) \le O(n)

    代码实现要点

    1. 数据结构

    int a[55];          // 原木板长度
    int b[1005];        // 需要木板长度
    int sum[1005];      // 需要木板前缀和
    int rest[55];       // 原木板剩余长度
    int n, m, total;    // 需要木板数、原木板数、原木板总长度
    

    2. 关键函数

    • bool check(int k): 验证能否得到 k 块需要木板
    • bool dfs(int now, int last, int waste): DFS 搜索可行性
    • 二分查找:寻找最大可行的 k

    3. 优化技巧

    1. 预处理排序和前缀和
    2. DFS 中的多重剪枝
    3. 记录上次使用的原木板编号
    4. 优先处理相等情况(零浪费)

    示例分析

    样例 1

    原木板: 25, 30, 40, 50
    需要木板: 15, 16, 17, 18, 19, 20, 21, 24, 25, 30
    答案: 7
    
    • 可以切割出:15, 16, 17, 18, 19, 20, 21(共7块)
    • 使用原木板合理分配,避免浪费

    样例 2

    原木板: 10, 10, 20
    需要木板: 3, 3, 3, 5, 5, 7, 8, 8, 9
    答案: 7
    
    • 可以切割出:3, 3, 3, 5, 5, 7, 8(共7块)
    • 充分利用每块原木板,最小化浪费

    算法标签

    • 二分
    • 深度优先搜索(DFS)
    • 剪枝优化
    • 贪心策略
    • 模拟

    总结

    本题通过二分答案将最优化问题转化为判定问题,再通过DFS+剪枝验证可行性。关键在于设计有效的剪枝策略:

    1. 浪费剪枝:基于总长度守恒
    2. 顺序剪枝:避免重复搜索相同状态
    3. 相等优先:零浪费切割最优

    这些优化使得在 m50,n1000m \le 50, n \le 1000 的数据范围内,算法能够在时限内运行。此题综合考察了二分答案、搜索剪枝和问题建模能力。

    • 1

    信息

    ID
    5980
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者