OD统一考试
题解: Java / Python / C++
题目描述
一根X米长的树木,伐木工切割成不同长度的木材后进行交易,交易价格为每根木头长度的乘积。规定切割后的每根木头长度都为正整数,也可以不切割,直接拿整根树木进行交易。请问伐木工如何尽量少的切割,才能使收益最大化?
输入描述
木材的长度(X<=50)
输出描述
输出最优收益时的各个树木长度,以空格分割,按升序排列
示例1
输入:
10
输出:
3 3 4
说明:
1.一根2米长的树木,伐木工不切割,为2*1,收益最大为2
2.一根4米长的树木,伐木工不需要切割为2*2,省去切割成本,直接整根树木交易,为4*1,收益最大为4
3.一根5米长的树木,伐木工切割为2*3,收益最大为 6
4.一根10米长的树木,伐木工可以切割为方式: 3,4,3,也可以切割为方式二:3,2,2,3,但方式二代木工多切割了一次增加切割成本却卖了一样的价格,因此并不是最优收益。
题解
动态规划类型的问题。
通过动态规划的方法:
1、定义一个状态数组
dp
,其中dp[x]
表示长度为x
的树木的最大化收益。2、定义一个数组
d
,其中 d[x] 用于记录长度为x
的树木达到最大收益时,最后一节的长度(收益相同取切割次数最少的)。3、定义
times
, 其中 times[x] 数组表示长度为x
的树木最小需要切割的次数。动态规划的状态转移方程为:dp[x]=max(dp[x],dp[j]×(x−j)), for j∈[1,x−1]
这表示尝试对长度为
x
的树木进行切割,寻找使收益最大的切割方式。最后,我们通过回溯
d
数组,获取切割的具体方式,并按升序排列输出。代码中,使用了两个数组
dp
和d
分别表示最大收益和最后一节的长度,遍历计算得到最优解。时间复杂度为 O(X^2),其中 X 为树木的长度。空间复杂度为 O(X)。
Java
java
import java.util.ArrayList;
import java.util.Collections;
import java.util.Scanner;
/**
* @author code5bug
*/
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int x = scanner.nextInt();
// dp[x] 表示长度为 x 的树木最大化的收益
int[] dp = new int[x + 1];
// times[x]使长度为 x 的树木最大化的收益,最小需要切割的次数
int[] times = new int[x + 1];
// d[x] 表示长度为 x 的树木要达到最大收益最后一节的长度
int[] d = new int[x + 1];
for (int i = 1; i <= x; i++) {
// 不切割时的收益
dp[i] = d[i] = i;
// 尝试对长度为 i 的树木进行切割以获取最大收益
for (int j = 1; j < i; j++) {
if (dp[i - j] * j > dp[i]) { // 切割出长度 j 的一段,判断是否能收益变大
d[i] = j;
dp[i] = dp[i - j] * j;
times[i] = times[i - j] + 1;
} else if (dp[i - j] * j == dp[i] && times[i] > times[i - j] + 1) { // 收益相同但切割次数少
d[i] = j;
times[i] = times[i - j] + 1;
}
}
}
int idx = x;
ArrayList<Integer> rs = new ArrayList<>();
while (idx > 0) {
rs.add(d[idx]);
idx -= d[idx];
}
Collections.sort(rs);
for (int i : rs) {
System.out.print(i + " ");
}
}
}
Python
python
x = int(input())
# dp[x] 表示长度为 x 的树木最大化的收益
dp = [i for i in range(x + 1)]
# 使长度为 x 的树木最大化的收益,最小需要切割的次数
times = [0] * (x + 1)
# d[x] 表示长度为 x 的树木要达到最大收益最后一节的长度
d = [i for i in range(x + 1)]
for i in range(1, x + 1):
for j in range(1, i): # 尝试对长度为 i 的树木进行切割以获取最大收益
if dp[i - j] * j > dp[i]: # 切割出长度 j 的一段,判断是否能收益变大
d[i] = j
dp[i] = dp[i-j] * j
times[i] = times[i-j] + 1
elif dp[i-j] * j == dp[i] and times[i] > times[i-j] + 1: # 收益相同但切割次数少
d[i] = j
dp[i] = dp[i-j] * j
times[i] = times[i - j] + 1
idx, rs = x, []
while idx > 0:
rs.append(d[idx])
idx -= d[idx]
rs.sort()
print(*rs)
C++
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int x;
cin >> x;
// dp[x] 表示长度为 x 的树木最大化的收益
vector<int> dp(x + 1);
// times[x] 使长度为 x 的树木最大化的收益,最小需要切割的次数
vector<int> times(x + 1);
// d[x] 表示长度为 x 的树木要达到最大收益最后一节的长度
vector<int> d(x + 1);
for (int i = 1; i <= x; i++) {
// 不切割时的收益
dp[i] = d[i] = i;
// 尝试对长度为 i 的树木进行切割以获取最大收益
for (int j = 1; j < i; j++) {
if (dp[i - j] * j > dp[i]) { // 切割出长度 j 的一段,判断是否能收益变大
d[i] = j;
dp[i] = dp[i - j] * j;
times[i] = times[i - j] + 1;
} else if (dp[i - j] * j == dp[i] && times[i] > times[i - j] + 1) { // 收益相同但切割次数少
d[i] = j;
times[i] = times[i - j] + 1;
}
}
}
int idx = x;
vector<int> rs;
while (idx > 0) {
rs.push_back(d[idx]);
idx -= d[idx];
}
sort(rs.begin(), rs.end());
for (int i : rs) {
cout << i << " ";
}
return 0;
}
🙏整理题解不易, 如果有帮助到您,请给点个赞 ❤️ 和收藏 ⭐,让更多的人看到。🙏🙏🙏