题目
题型分析
这是比较典型的动态规划的问题。动态规划是什么呢?本质上动态规划是对递归的优化。例如,兔子数列:f(x) = f(x - 1) + f(x -2), 我们知道 f 代表了计算公式,这里解放思想一下,如果 f 替换为数组,将每个 f(x) 的返回值返回,那么是不是就不用计算了,直接返回了,这样就节省了 CPU 递归和计算的时间,提高计算的效率,这样就对递归进行了优化。
public static fb(int input){
if(input == 1){
return 1 ;
}
if(input == 2){
return 2 ;
}
return fb(input -1) + fb(input - 2);
}
改成动态规划:
public static fb(int input){
int[] dp = new int[input + 1];
dp[1] = 1 ;
dp[2] = 2 ;
for(int i = 3 ; i <= input ; i++){
dp[i] = dp[i - 1] + dp[i - 2] ;
}
return dp[input] ;
}
斐波那契数列只是一个数列而已。只是我们可以从中总结出一些规律:
- 找到初始值。这就是递归的出口。
- 然后以初始值为起点,往后继续计算。这代表了各个递归调用。
说完斐波那契数列,我们可以把难度增加。经典的 0-1 背包问题。它题目往往是这样有两个一维数组,第一个代表了容量,第二个代表价值,给定一个 N 正整数值,N 值是背包能装的容量,数组的一个元素代表了一个物品,一个物品只能装一个,问背包能装下的最大价值是多少?
递归的算法:
定义 process(int i , int N , int[][] capacity , int[][] value) , 其中 i 代表 i ~ capacity.length 容量为 N 的情况下,返回最大的价值。
于是可以按两种情况处理。
- 不要 i 物品, 那么递归应该 process(i , N ) = process(i + 1 , N)
- 要 i 物品,那么递归应该 process(i , N) = value[i] + process(i + 1 , N - capacity[i])
- 然后取里面最大的那个。
有了上面的分析,递归可以写为:
public static int process(int[] weight, int[] value, int startIdx, int bagWeight) {
if (startIdx == weight.length - 1) {
return (weight[startIdx] <= bagWeight ? value[startIdx] : 0);
}
if (bagWeight == 0) {
return 0;
}
// 不将 startIdx 的物品放入背包
int v1 = process(weight, value, startIdx + 1, bagWeight);
int v2 = 0;
if (bagWeight >= weight[startIdx]) {
v2 = process(weight, value, startIdx + 1, bagWeight - weight[startIdx])
+ value[startIdx];
}
return Math.max(v2, v1);
}
同样的如果将 process 看成是数组,那么可以想象:
- 可以先计算出 dp[capacity.length -1 ][0 … N] 的初始中,代表的业务含义是当容量从 0 到 N 时的最大价值。
- 我们可以看出 i 的值是依赖于 i + 1 的值,所以 dp[capacity.length - 2 ][0 … N] 也可以计算出来,一次类推,数组的所有需要的值都可以计算出来。
- 最后返回的是 dp[0][N] 的值就可以了。
于是动态规划的版本如下所示:
public static int processWithDp(int[] w , int[] v , int bag){
int[][] dp = new int[w.length+1][bag+1];
int p1 = 0 ;
int p2 = 0 ;
for(int index = w.length - 1 ; index >=0 ; index--){
for(int rest = 0 ; rest <= bag ; rest++){
p1 = dp[index+1][rest];
p2 = 0 ;
if(rest >= w[index]){
p2 = dp[index+1][rest - w[index]] + v[index];
}
dp[index][rest] = Math.max(p1 , p2);
}
}
return dp[0][bag] ;
}
最后说到这个题目,此题是在 0-1 背包的基础上增加了依赖。从题目中,可以知道附件不能离开主件独立存在,所以可以先以主件分组,A1{main , fj1 , fj2 } , A2{main , fj1 , fj2} … 在计算的时候,取下面的最大值。
- 不要 i 主件物品, A(i , N) = A(i +1 , N)
- 只要 i 主件物品,A(i , N) = A(i + 1 , N - A(i)的价格)
- 只要 i 主件、fj1, A(i , N) = A(i + 1 , N - A(i)的价格 - fj1 的价格)
- 只要 i 主件、fj2, A(i , N) = A(i + 1 , N - A(i)的价格 - fj2 的价格)
- i 主件、fj1、fj2, A(i , N) = A(i + 1 , N - A(i)的价格 - fj1 的价格 - fj2 的价格)
根据这样的理解,编写如下代码:
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
// 注意 hasNext 和 hasNextLine 的区别
int N = 0 ;
int m = 0 ;
List<Product> products = new ArrayList();
if(in.hasNextInt()) { // 注意 while 处理多个 case
N = in.nextInt();
m = in.nextInt();
}
for(int a = 1 ; a <= m ; a++){
products.add(new Product(a ,in.nextInt() , in.nextInt() , in.nextInt()));
}
int[][] value = new int[products.size()][3];
int[][] price = new int[products.size()][3];
List<Product> main = component(value , price , products);
System.out.println(maxSatisfiedSocre2(N , main , value , price));
}
public static List<Product> component(int[][] value , int[][] price , List<Product> products){
List<Product> main = new ArrayList();
int j = 0 ;
for(int i = 0 ; i < products.size() ; i++){
if(products.get(i).q == 0){
main.add(products.get(i));
value[j][0] = products.get(i).v;
price[j][0] = products.get(i).p;
List<Product> c = getFj(products.get(i).idx , products);
if(c.size() > 0){
value[j][1] = c.get(0).p;
price[j][1] = c.get(0).v;
if(c.size() > 1 ){
value[j][2] = c.get(1).p;
price[j][2] = c.get(1).v;
}
}
j++;
}
}
return main ;
}
public static List<Product> getFj(int id , List<Product> products){
List<Product> rs = new ArrayList<Product>();
for(int i = 0 ; i < products.size() ; i++){
if(id == products.get(i).q){
rs.add(products.get(i));
}
}
return rs ;
}
public static class Product{
public int idx ;
public int v ;
public int p ;
public int q ;
public Product(int idx ,int v , int p , int q){
this.idx = idx ;
this.v = v ;
this.p = p ;
this.q = q ;
}
}
// 递归版本,此解法会超时
public static int maxSatisfiedSocre(int N ,List<Product> products , int[][] value , int[][] price){
if(N == 0 || null == products || products.size() == 0) return 0 ;
return process(0 , N , products , value , price);
}
// 动态递归的解法
public static int maxSatisfiedSocre2(int N ,List<Product> products , int[][] value , int[][] price){
if(N == 0 || null == products || products.size() == 0) return 0 ;
int[][] dp = new int[products.size()][N+1];
int p1 = -1 ;
int p2 = -1 ;
int p3 = -1 ;
int p4 = -1 ;
int idx = products.size()-1 ;
// int j = N ;
for(int j = N ; j >= products.get(idx).v ; j-- ){
p1 = -1 ;
p2 = -1 ;
p3 = -1 ;
p4 = -1 ;
if( j >= products.get(idx).v){
p1 = products.get(idx).v*products.get(idx).p ;
// dp[idx][j - products.get(idx).v] = p1 ;
}
if(j >= products.get(idx).v + price[idx][1]){
p2 = products.get(idx).v*products.get(idx).p + value[idx][1]*price[idx][1] ;
// dp[idx][j - products.get(idx).v - price[idx][1]] = p2 ;
}
if(j >= products.get(idx).v + price[idx][2]){
p3 = products.get(idx).v*products.get(idx).p + value[idx][2]*price[idx][2] ;
// dp[idx][j - products.get(idx).v - price[idx][2]] = p3 ;
}
if(j >= products.get(idx).v + price[idx][1] + price[idx][2]){
p4 = products.get(idx).v*products.get(idx).p + value[idx][1]*price[idx][1] + value[idx][2]*price[idx][2] ;
// dp[idx][j - products.get(idx).v - price[idx][1] - price[idx][2]] = p4 ;
}
dp[idx][j] = Math.max(Math.max(p1 , p2) , Math.max(p3 , p4));
}
int p5 = -1 ;
for(int i = products.size() - 2 ; i>=0 ; i--){
for(int j = N ; j > 0 ; j--){
p1 = -1 ;
p2 = -1 ;
p3 = -1 ;
p4 = -1 ;
p5 = -1 ;
if( j >= products.get(i).v ){
p1 = products.get(i).v*products.get(i).p + dp[i+1][j - products.get(i).v];
// dp[i][j - products.get(i).v] = p1 ;
}
if(j >= products.get(i).v + price[i][1]){
p2 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] + dp[i+1][j - products.get(i).v - price[i][1]];
// dp[i][j - products.get(i).v - price[i][1]] = p2;
}
if(j >= products.get(i).v + price[i][2]){
p3 = products.get(i).v*products.get(i).p + value[i][2]*price[i][2] + dp[i+1][j - products.get(i).v - price[i][2]];
// dp[i][j - products.get(i).v - price[i][2]] = p3 ;
}
if(j >= products.get(i).v + price[i][1] + price[i][2]){
p4 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] + value[i][2]*price[i][2] + dp[i+1][j - products.get(i).v - price[i][1] - price[i][2]];
// dp[i][j - products.get(i).v - price[i][1] - price[i][2] ] = p4 ;
}
// 不要 i
p5 = dp[i+1][j];
dp[i][j] = Math.max(Math.max(p1 , p2) , Math.max(Math.max(p3 , p4) , p5));
}
}
return dp[0][N];
}
// 递归版本的实际实现。
public static int process(int i , int N , List<Product> products , int[][] value , int[][] price){
if(i + 1 == products.size()){
int p1 = -1 ;
int p2 = -1 ;
int p3 = -1 ;
int p4 = -1 ;
if( N >= products.get(i).v){
p1 = products.get(i).v*products.get(i).p ;
}
if(N >= products.get(i).v + price[i][1]){
p2 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] ;
}
if(N >= products.get(i).v + price[i][2]){
p3 = products.get(i).v*products.get(i).p + value[i][2]*price[i][2] ;
}
if(N >= products.get(i).v + price[i][1] + price[i][2]){
p4 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] + value[i][2]*price[i][2] ;
}
return Math.max(Math.max(p1 , p2) , Math.max(p3 , p4));
}
int p1 = -1 ;
int p2 = -1 ;
int p3 = -1 ;
int p4 = -1 ;
// 只要 i 的作为主键
int next = process(i+1 , N - products.get(i).v , products , value , price);
if(-1 == next){
next = 0 ;
}
if( N >= products.get(i).v ){
p1 = products.get(i).v*products.get(i).p + next ;
}
if(N >= products.get(i).v + price[i][1]){
next = process(i+1 , N - products.get(i).v - price[i][1] , products , value , price);
if(-1 == next){
next = 0 ;
}
p2 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] + next ;
}
if(N >= products.get(i).v + price[i][2]){
next = process(i+1 , N - products.get(i).v - price[i][2] , products , value , price);
if(-1 == next){
next = 0 ;
}
p3 = products.get(i).v*products.get(i).p + value[i][2]*price[i][2] + next;
}
if(N >= products.get(i).v + price[i][1] + price[i][2]){
next = process(i+1 , N - products.get(i).v - price[i][1] - price[i][2] , products , value , price);
if(-1 == next){
next = 0 ;
}
p4 = products.get(i).v*products.get(i).p + value[i][1]*price[i][1] + value[i][2]*price[i][2] + next;
}
// 不要 i
int p5 = process(i+1 , N , products , value , price);
return Math.max(Math.max(p1 , p2) , Math.max(p5 , Math.max(p3 , p4)));
}
}