动态规划:(二)背包问题 —— 1. 01 背包模型

本文详细介绍了动态规划在解决01背包问题、装箱问题和宠物小精灵收服问题中的应用,通过状态表示、状态转移和优化实现,展示了如何在不同场景下找到最优解。同时,还探讨了数字组合问题的解决方案,通过状态转移矩阵计算特定数值组合的方案数。这些算法在资源有限的情况下,帮助最大化效益或效率。
摘要由CSDN通过智能技术生成

最大值/最小值

01 背包问题

分析

状态表示

  • v ( i ) v(i) v(i):第 i i i 个物体体积
  • w ( i ) w(i) w(i):第 i i i 个物体价值
  • f ( i , j ) f(i,j) f(i,j):在前 i i i 个物品中选,体积不超过 j j j 的最大价值

状态转移

  • 可从不选前一个物品转移。即可从 f ( i − 1 , j ) f(i - 1, j) f(i1,j) 转移
  • 前一个物体要能放入背包,才可从选前一个物体转移。即当 j ≥ v ( i ) j \ge v(i) jv(i) 时,可从 f ( i − 1 , j − v ( i ) ) f(i - 1, j - v(i)) f(i1,jv(i)) 转移

代码

import java.util.*;

public class Main {
  public static int dp(int[] v, int[] w, int n, int m) {
    int[][] f = new int[n + 1][m + 1];
    for (int i = 1; i <= n; i++) {
      for (int j = 0; j <= m; j++) {
        f[i][j] = f[i - 1][j];
        if (j >= v[i]) f[i][j] = Math.max(f[i - 1][j - v[i]] + w[i], f[i][j]);
      }
    }
    return f[n][m];
  }
  
  // for IO
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int m = scanner.nextInt();
    int[] v = new int[n + 1];
    int[] w = new int[n + 1];
    for (int i = 1; i <= n; i++) {
      v[i] = scanner.nextInt();
      w[i] = scanner.nextInt();
    }
    System.out.println(dp(v, w, n, m));
  }
}

优化

直接将数组降一维,给代码做等价变形

public static int dp(int[] v, int[] w, int n, int m) {
  int[] f = new int[m + 1];
  for (int i = 1; i <= n; i++) {
    for (int j = m; j >= 0 && j >= v[i]; j--) {
      f[j] = Math.max(f[j - v[i]] + w[i], f[j]);
    }
  }
  return f[m];
}

采药

本质就是一个 01 背包

装箱问题

分析

问题转换:任取若干个装入箱内,使箱子的剩余空间为最小 -> 不超过总容量的情况下,使得占用箱子的体积最大

状态表示

  • v ( i ) v(i) v(i):第 i i i 个物体体积
  • f ( i , j ) f(i,j) f(i,j):在前 i i i 个物体中选,体积不超过 j j j 的最大体积

状态转移

  • 可以从不选前一个物体转移。即可从 f ( i − 1 , j ) f(i - 1, j) f(i1,j) 转移
  • 前一个物体要能放入箱子时,才可从选前一个物体转移。即当 j ≥ v ( i ) j \ge v(i) jv(i) 时,可从 f ( i − 1 , j − v ( i ) ) f(i - 1, j - v(i)) f(i1,jv(i)) 转移

代码

import java.util.*;

public class Main {
  public static int dp(int[] v, int n, int m) {
    int[][] f = new int[n + 1][m + 1];
    for (int i = 1; i <= n; i++) {
      for (int j = 0; j <= m; j++) {
        f[i][j] = f[i - 1][j];
        if (j >= v[i]) f[i][j] = Math.max(f[i - 1][j - v[i]] + v[i], f[i][j]);
      }
    }
    return m - f[n][m];
  }
  
  // for IO
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int m = scanner.nextInt();
    int n = scanner.nextInt();
    int[] v = new int[n + 1];
    for (int i = 1; i <= n; i++) {
      v[i] = scanner.nextInt();
    }
    System.out.println(dp(v, n, m));
  }
}

优化

public static int dp(int[] v, int n, int m) {
  int[] f = new int[m + 1];
  for (int i = 1; i <= n; i++) {
    for (int j = m; j >= 0; j--) {
      if (j >= v[i]) f[j] = Math.max(f[j - v[i]] + v[i], f[j]);
    }
  }
  return m - f[m];
}

宠物小精灵之收服

分析

状态表示

  • a ( i ) a(i) a(i) :抓捕第 i i i 个精灵需要的精灵球数量
  • b ( i ) b(i) b(i) :抓捕第 i i i 个精灵需要损耗皮卡丘的血量
  • f ( i , j , k ) f(i,j,k) f(i,j,k) :在前 i i i 个精灵中选,使用精灵球不超过 j j j,皮卡丘血量大于 k k k 时最大捕捉数

状态转移

  • 可从不抓前一个精灵转移。即可从 f ( i − 1 , j , k ) f(i - 1, j, k) f(i1,j,k) 转移
  • 当精灵球数量与皮卡丘血量经得起消耗时,可从抓前一个精灵转移。即当满足 j ≥ a ( i ) j \ge a(i) ja(i) k > b ( i ) k \gt b(i) k>b(i) 时,可从 f ( i − 1 , j − a ( i ) , k − b ( i ) ) f(i - 1,j-a(i),k-b(i)) f(i1,ja(i),kb(i)) 转移

状态计算

  • 最大捕捉数
  • 最大捕捉数下时的皮卡丘最多血量

代码

import java.util.*;

public class Main {
  public static int[] dp(int n, int m, int l, int[] a, int[] b) {
    int[][][] f = new int[l + 1][n + 1][m + 1];
    
    for (int i = 1; i <= l; i++) {
      for (int j = 0; j <= n; j++) {
        for (int k = 0; k <= m; k++) {
          f[i][j][k] = f[i - 1][j][k];
          if (j >= a[i] && k > b[i]) {
            f[i][j][k] = Math.max(f[i - 1][j - a[i]][k - b[i]] + 1, f[i - 1][j][k]);
          }
        }
      }
    }
    
    int cnt = f[l][n][m];
    int t = m;
    for (int j = 0; j <= n; j++) {
      for (int k = 0; k <= m; k++) {
        if (f[l][j][k] == cnt) {
          t = Math.min(t, k);
        }
      }
    }
    
    return new int[] {cnt, cnt == 0 ? m : m - t + 1};
  }
  
  // for IO
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int m = scanner.nextInt();
    int l = scanner.nextInt();
    int[] a = new int[l + 1];
    int[] b = new int[l + 1];
    for (int i = 1; i <= l; i++) {
      a[i] = scanner.nextInt();
      b[i] = scanner.nextInt();
    }
    int[] res = dp(n, m, l, a, b);
    System.out.println(res[0] + " " + res[1]);
  }
}

优化

public static int[] dp(int n, int m, int l, int[] a, int[] b) {
  int[][] f = new int[n + 1][m + 1];

  for (int i = 1; i <= l; i++) {
    for (int j = n; j >= 0 && j >= a[i]; j--) {
      for (int k = m; k >= 0 && k > b[i]; k--) {
        f[j][k] = Math.max(f[j - a[i]][k - b[i]] + 1, f[j][k]);
      }
    }
  }

  int cnt = f[n][m];
  int t = m;
  for (int j = 0; j <= n; j++) {
    for (int k = 0; k <= m; k++) {
      if (f[j][k] == cnt) {
        t = Math.min(t, k);
      }
    }
  }

  return new int[] {cnt, cnt == 0 ? m : m - t + 1};
}

求方案数

数字组合

分析

状态表示

  • q ( i ) q(i) q(i) :第 i i i 个数的数值
  • f ( i , j ) f(i,j) f(i,j):前 i i i 个数中,选择的数之和为 j j j 的方案数

状态转移

  • 可从不选前一个数转移。即从 f ( i − 1 , j ) f(i - 1, j) f(i1,j) 转移
  • 只有当前一个数能选择时,才可从选前一个数转移。即当满足 j ≥ q ( i ) j \ge q(i) jq(i) 时,才能从 f ( i − 1 , j − q ( i ) ) f(i-1,j-q(i)) f(i1,jq(i)) 转移

代码

import java.util.*;

public class Main {
  public static int dp(int[] q, int n, int m) {
    int[][] f = new int[n + 1][m + 1];
    for (int i = 0; i <= n; i++) f[i][0] = 1; // 选择的数之和为 0 的方案数为 1,因为什么都不选也算是一种方案
    for (int i = 1; i <= n; i++) {
      for (int j = 0; j <= m; j++) {
        f[i][j] = f[i - 1][j];
        if (j >= q[i]) f[i][j] += f[i - 1][j - q[i]];
      }
    }
    return f[n][m];
  }
  
  // for IO
  public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    int n = scanner.nextInt();
    int m = scanner.nextInt();
    int[] q = new int[n + 1];
    for (int i = 1; i <= n; i++) {
      q[i] = scanner.nextInt();
    }
    System.out.println(dp(q, n, m));
  }
}

优化

public static int dp(int[] q, int n, int m) {
  int[] f = new int[m + 1];
  f[0] = 1;
  for (int i = 1; i <= n; i++) {
    for (int j = m; j >= 0 && j >= q[i]; j--) {
      f[j] += f[j - q[i]];
    }
  }
  return f[m];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值