一、题目

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

输入描述:
木材的长度(X<=50)

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

示例1
输入:
10
输出:
3 3 4
说明:

  1. 一根2米长的树木,伐木工不切割,为21,收益最大为2
  2. 一根4米长的树木,伐木工不需要切割为22,省去切割成本,直接整根树木交易,为41,收益最大为4
  3. 一根5米长的树木,伐木工切割为23,收益最大为 6
  4. 一根10米长的树木,伐木工可以切割为方式: 3, 4, 3,也可以切割为方式二: 3, 2, 2, 3,但方式二伐木工多切割了一次增加切割成本却卖了一样的价格,因此并不是最优收益。

二、题目分析与解题思路🧠

这道题本质上是经典的整数拆分问题的变种,核心在于通过数学规律找到贪心策略

1. 核心数学规律推导

我们要把 nn 拆分成 a1+a2+...+ak ,使得 P=a1×a2×...×ak 最大。

  • 避免长度为 1:任何数乘以 1 都不会变大(1×n=n ),反而浪费长度,所以因子中不能有 1。
  • 优先切分为 3
    • 当 n≥5 时,我们可以证明 3(n−3)>n 。
    • 例如:5→2+3(6>5),6→3+3(9>6) 。
    • 这意味着,只要长度大于等于 5,我们就应该把它切分,且切出一个 3 是最优的。
  • 关于 4 的处理
    • 4 可以拆成 2+2 ,乘积 2×2=4 ,与原值相等。
    • 关键点:题目要求“尽量少切割”。既然 4 切割后收益不增,我们选择保留 4 不切(或者理解为拆成 2+2 后,在输出时合并)。
    • 注意:在纯数学的“整数拆分”题中,通常 4 会被视为 2+2 ,但本题有“最少切割”约束,且 2×2=4 ,所以保留 4或拆成 2,2 对乘积无影响,但对切割次数有影响。但在贪心策略中,当剩余长度为 4 时,直接保留即可。
2. 贪心算法步骤
  1. 特殊情况:若 X≤4 ,直接输出 X (因为切割不会增加收益,反而增加成本)。
  2. 循环切分:当 X>4 时,不断切出长度为 3 的段,直到剩余长度 ≤4 。
  3. 处理剩余:将剩余的长度(可能是 2, 3, 或 4)作为最后一段加入结果。
  4. 排序输出:题目要求升序排列。

三、Java 实现🚀

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;

public class WoodCutting {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNext()) {
            int X = scanner.nextInt();
            List<Integer> result = new ArrayList<>();

            // 1. 处理特殊情况:X <= 4 时,切割不会增加收益,且会增加切割成本
            if (X <= 4) {
                System.out.println(X);
                return;
            }

            // 2. 贪心策略:只要长度大于4,就优先切出长度为3的段
            // 因为 3*(X-3) > X (当X>=5时)
            while (X > 4) {
                result.add(3);
                X -= 3;
            }

            // 3. 处理剩余部分(此时 X 必然是 2, 3 或 4)
            // 注意:如果是4,保留为4(相当于2+2但不切),符合“最少切割”原则
            if (X > 0) {
                result.add(X);
            }

            // 4. 升序排序并输出
            Collections.sort(result);
            for (int i = 0; i < result.size(); i++) {
                System.out.print(result.get(i));
                if (i < result.size() - 1) {
                    System.out.print(" ");
                }
            }
        }
        scanner.close();
    }
}

四、Go 实现🚀

package main

import (
    "fmt"
    "sort"
)

func main() {
    var X int
    fmt.Scan(&X)
    var result []int

    // 1. 特殊情况处理
    if X <= 4 {
        fmt.Println(X)
        return
    }

    // 2. 贪心切分:优先切出 3
    for X > 4 {
        result = append(result, 3)
        X -= 3
    }

    // 3. 处理剩余长度
    if X > 0 {
        result = append(result, X)
    }

    // 4. 排序输出
    sort.Ints(result)
    for i, v := range result {
        fmt.Print(v)
        if i < len(result)-1 {
            fmt.Print(" ")
        }
    }
}
复杂度分析
  • 时间复杂度: O(X/3) ,即 O(X)。由于题目限制 X≤50 ,循环次数极少,几乎是瞬间完成。排序的时间复杂度为 O(Klog⁡K) ,其中 K 是段数,也非常小。
  • 空间复杂度: O(X/3) ,用于存储结果列表。

四、总结📝

本题是经典的贪心算法应用。解题的关键在于识别出数学规律:尽可能多地切出长度为 3 的段,同时处理好边界条件(特别是长度为 4 时的“最少切割”约束)。掌握这个规律后,代码实现非常简单。

小贴士:在机考中遇到此类“最大值/最优解”问题,先尝试列举前几个数字(如 2, 3, 4, 5, 6)找规律,往往比直接想动态规划更高效。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐