给定一根长度为N米的原木;另有一个分段价格表,给出长度L1,L2,...Li,...Lk米所对应的价格P1,P2...Pk(Li,Pi均为正整数),求切割原木分段出售所能获得的最大收益。 例如,根据下面给出的价格表,
Li | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|
Pi | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 | 23 | 28 |
若要出售一段8米长的原木,最优解是将其切割为2米和6米的两段,这样可以获得最大收益=L2+L6=5+17=22。而若要出售一段3米长的原木,最优解是根本不要切割,直接售出。
输入格式:
首行输入N,k,紧接着第二行为k个Li(递增有序)和第三行对应的k个Pi值。 (0<N,k<1000) 。
输出格式:
对应原木的最大切割收益(题目中保证最大收益值在int范围)。
输入样例:
在这里给出一组输入。例如:
8 10
1 2 3 4 5 6 7 8 9 10
1 5 8 9 10 17 17 20 23 28
输出样例:
在这里给出相应的输出。例如:
22
解题思路:
经典动态规划问题,要想得到最佳解,在每求一步时都必须充分利用之前的局部解,其中最关键的是要列出动态转移方程。这道题可以假设从第一米到第n米,每一米先只用一种切割方式时,求出各米的最佳收益,存放至一维dp数组中。然后再加一种切割方式,此时在切割时,有种错误的思路是全用新的切割方式,切的剩下的在dp数组中取值,这种思路一定是错误的,因为可能在切割时,只是新切割方式用一次或两次收益即可达到最大值。正确的切割方式为:仍从第一米到第n米,在每次切割的基础上都只用一次新切割方式,切掉新方式时剩余的dp值加上此次切割的收益,两者相加与原始dp相比取最大值。即得到转移方程:dp[j] = max(dp[j - t[i]] + p[i] , dp[j])。
提给出得样例的dp二维数组为:
在列二维数组时要注意将第零行和第零列空出来,每一横行表示用一种切割方式切不同长度的木材价值,每一纵列表示某米长的木材用不同的方式切割可以取到的价值。
核心java代码为:(二维dp)
for(int i = 0; i < m;i++) {
for(int j = 1;j <= n;j++) {
if(j < t[i]) {//填充不足t[i]米时的情况
dp[i + 1][j] = dp[i][j];
}else {
dp[i + 1][j] = Math.max(dp[i][j], dp[i + 1][j - t[i]] + p[i]);
}
}
}
二维数组会有较大空间浪费,可优化为一维数组:dp[n + 1] 只保存每米切割可以取到的最大值,对其余值不保存。
java代码:(一维dp)
import java.util.Scanner;
public class Main {
public static void main(String[] args){
Scanner scan = new Scanner(System.in);
int n = scan.nextInt();//木头长度
int m = scan.nextInt();//题给出的数组长度
int []t = new int[m];
int []p = new int[m];
int []dp = new int[n + 1];
for(int i = 0; i < m;i++) {
t[i] = scan.nextInt();
}
for(int i = 0; i < m;i++) {
p[i] = scan.nextInt();
}
scan.close();
for(int i = 0; i < m;i++) {
for(int j = t[i];j <= n;j++) {
dp[j] = Math.max(dp[j], dp[j - t[i]] + p[i]);
}
}
System.out.println(dp[n]);
}
}