【OD机试题解法笔记】伐木工

题目


一根X米长的树木,伐木工切割成不同长度的木材后进行交易,交易价格为每根木头长度的乘积。规定切割后的每根木头长度都为正整数;也可以不切割,直接拿整根树木进行交易。
请问伐木工如何尽量少的切割,才能使收益最大化?

输入描述
木材的长度(X ≤ 50)

输出描述
输出最优收益时的各个树木长度,以空格分隔,按升序排列

用例

输入输出
103 3 4

思考

题目类似《算法导论》中介绍的钢条切割问题,使用动态规划求解。木头的长度是规模,长度为N的木头可以在1~N-1长度之间选择切还是不切,每个切割后的木头本身又是一个规模更小的子问题。因此选择自底向上不断迭代计算缓存规模较小的子问题,最终最长整个问题(规模为N)的解。

算法过程

  1. 初始化

    • 当长度为 1 时,无法切割(切割后段长需为正整数),故:
      • dp[1] = 1(收益为自身长度)。
      • counts[1] = 1(段数为 1,切割次数 0)。
      • cuts[1] = [1](唯一方案)。
  2. 自底向上遍历长度 从长度 \(i = 2\) 到 \(i = X\),依次求解每个长度的最优解:

    • 初始默认方案:不切割,直接取整根木材,故:

      • dp[i] = i(收益为自身长度)。
      • counts[i] = 1(段数为 1)。
      • cuts[i] = [i](仅自身一段)。
    • 枚举切割点 对于每个长度 i,枚举所有可能的切割点 j(\(1 \leq j < i\)),将木材切分为长度 j 和 \(i-j\) 的两段:

      • 子问题收益:两段的收益为 dp[j] * dp[i-j]
      • 段数:两段的总段数为 counts[j] + counts[i-j]
    • 更新最优解

      • 情况 1:新收益更大 若 dp[j] * dp[i-j] > dp[i],则更新:
        • dp[i] 为新收益。
        • counts[i] 为总段数。
        • cuts[i] 为两段的长度合并后排序的结果。
      • 情况 2:收益相同但段数更少 若 dp[j] * dp[i-j] === dp[i] 且总段数更少(counts[j] + counts[i-j] < counts[i]),则更新:
        • counts[i] 为更少的段数。
        • cuts[i] 为两段的长度合并后排序的结果。
关键优化点
  • 优先保证收益最大:在枚举切割点时,只有当新收益严格大于当前收益时,才无条件更新方案。
  • 同等收益下最小化段数:当收益相同时,选择段数更少的方案(切割次数更少),例如:
    • 长度为 4 时,方案 2+2(段数 2,乘积 4)和方案 1+3(段数 2,乘积 3),前者段数相同但收益更高,故选择前者。
    • 长度为 6 时,方案 3+3(段数 2,乘积 9)和方案 2+2+2(段数 3,乘积 8),前者收益更高,故选择前者。

该算法通过动态规划高效求解了最优切割方案,时间复杂度为 \(O(X^2)\),空间复杂度为 \(O(X)\),适用于 \(X \leq 50\) 的输入规模。

参考代码


function solution(n) {
    let dp = Array(n+1).fill(0);
    let counts = Array(n+1).fill(0);
    let cuts = Array(n+1);

    dp[1] = 1;
    counts[1] = 1;
    cuts[1] = [1];

    for (let i = 2; i <= n; i++) {
        dp[i] = i;
        counts[i] = 1;
        cuts[i] = [i];
        for (let j = 1; j < i; j++) {
            const k = i - j;
            const profit = dp[j] * dp[k];
            const count = counts[j] + counts[k]
            if (profit > dp[i] ) {
                dp[i] = profit;
                counts[i] = count;
                cuts[i] = [...cuts[j], ...cuts[k]].sort();
            } else if (profit === dp[i]) {
                if (count < counts[i]) {
                    counts[i] = count;
                    cuts[i] = [...cuts[j], ...cuts[k]].sort();
                }
            }
        }

    } 

    return cuts[n].join(' ');
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值