最大值/最小值
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(i−1,j) 转移
- 前一个物体要能放入背包,才可从选前一个物体转移。即当 j ≥ v ( i ) j \ge v(i) j≥v(i) 时,可从 f ( i − 1 , j − v ( i ) ) f(i - 1, j - v(i)) f(i−1,j−v(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(i−1,j) 转移
- 前一个物体要能放入箱子时,才可从选前一个物体转移。即当 j ≥ v ( i ) j \ge v(i) j≥v(i) 时,可从 f ( i − 1 , j − v ( i ) ) f(i - 1, j - v(i)) f(i−1,j−v(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(i−1,j,k) 转移
- 当精灵球数量与皮卡丘血量经得起消耗时,可从抓前一个精灵转移。即当满足 j ≥ a ( i ) j \ge a(i) j≥a(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(i−1,j−a(i),k−b(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(i−1,j) 转移
- 只有当前一个数能选择时,才可从选前一个数转移。即当满足 j ≥ q ( i ) j \ge q(i) j≥q(i) 时,才能从 f ( i − 1 , j − q ( i ) ) f(i-1,j-q(i)) f(i−1,j−q(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];
}