有依赖的的动态规划问题

题目

在这里插入图片描述
在这里插入图片描述

题型分析

这是比较典型的动态规划的问题。动态规划是什么呢?本质上动态规划是对递归的优化。例如,兔子数列: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] ;
}

斐波那契数列只是一个数列而已。只是我们可以从中总结出一些规律:

  1. 找到初始值。这就是递归的出口。
  2. 然后以初始值为起点,往后继续计算。这代表了各个递归调用。

说完斐波那契数列,我们可以把难度增加。经典的 0-1 背包问题。它题目往往是这样有两个一维数组,第一个代表了容量,第二个代表价值,给定一个 N 正整数值,N 值是背包能装的容量,数组的一个元素代表了一个物品,一个物品只能装一个,问背包能装下的最大价值是多少?
递归的算法:
定义 process(int i , int N , int[][] capacity , int[][] value) , 其中 i 代表 i ~ capacity.length 容量为 N 的情况下,返回最大的价值。
于是可以按两种情况处理。

  1. 不要 i 物品, 那么递归应该 process(i , N ) = process(i + 1 , N)
  2. 要 i 物品,那么递归应该 process(i , N) = value[i] + process(i + 1 , N - capacity[i])
  3. 然后取里面最大的那个。

有了上面的分析,递归可以写为:

    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 看成是数组,那么可以想象:

  1. 可以先计算出 dp[capacity.length -1 ][0 … N] 的初始中,代表的业务含义是当容量从 0 到 N 时的最大价值。
  2. 我们可以看出 i 的值是依赖于 i + 1 的值,所以 dp[capacity.length - 2 ][0 … N] 也可以计算出来,一次类推,数组的所有需要的值都可以计算出来。
  3. 最后返回的是 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} … 在计算的时候,取下面的最大值。

  1. 不要 i 主件物品, A(i , N) = A(i +1 , N)
  2. 只要 i 主件物品,A(i , N) = A(i + 1 , N - A(i)的价格)
  3. 只要 i 主件、fj1, A(i , N) = A(i + 1 , N - A(i)的价格 - fj1 的价格)
  4. 只要 i 主件、fj2, A(i , N) = A(i + 1 , N - A(i)的价格 - fj2 的价格)
  5. 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(
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值