# DP
### 01背包
> 有 $N$ 件物品和一个容量是 $V$ 的背包。每件物品只能使用一次。
>
> 第 $i$ 件物品的体积是 $vi$,价值是 $w_i$。
>
> 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
> 输出最大价值。
>
> #### 输入格式
>
> 第一行两个整数,$N,V$,用空格隔开,分别表示物品数量和背包容积。
>
> 接下来有 $N$ 行,每行两个整数 $v_i,w_i$,用空格隔开,分别表示第 $i$ 件物品的体积和价值。
>
> #### 输出格式
>
> 输出一个整数,表示最大价值。
>
> #### 数据范围
>
> $0<N,V≤1000$
> $0<v_i,w_i≤1000$
>
> #### 输入样例
>
> ```
> 4 5
> 1 2
> 2 4
> 3 4
> 4 5
> ```
>
> #### 输出样例:
>
> ```
> 8
> ```
```java
public class Beg_01 {
final static int N = 10010;
final static int[] w = new int[N], v = new int[N];
// 未优化版
final static int[][] f = new int[N][N];
// 优化版
final static int[] g = new int[N];
public static void main(String[] args) {
Scanner cin = new Scanner(System.in);
int n = cin.nextInt(), m = cin.nextInt();
for (int i = 1; i <= n; i++) {
v[i] = cin.nextInt(); w[i] = cin.nextInt();
}
// 朴素版
prime(n, m);
// 优化版
optimization(n, m);
}
private static void prime(int n, int m) {
// i 指向第 i 个物品
for (int i = 1; i <= n; i++) {
// j 指向容量
for (int j = 1; j <= m; j++) {
// 没有将第 i 个物品加入背包的情况
f[i][j] = f[i - 1][j];
// 将第 i 个物品加入背包的情况
if (v[i] <= j) f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
}
System.out.println(f[n][m]);
}
private static void optimization(int n, int m) {
// i 指向第 i 个物品
for (int i = 1; i <= n; i++) {
// j 指向容量
for (int j = m; j >= v[i]; j--) {
// 将第 i 个物品加入背包的情况
g[j] = Math.max(g[j], g[j - v[i]] + w[i]);
}
}
System.out.println(g[m]);
}
}
```
### 完全背包
> 有 $N$ 种物品和一个容量是 $V$ 的背包,每种物品都有无限件可用。
>
> 第 $i$ 种物品的体积是 $v_i$,价值是 $w_i$。
>
> 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
> 输出最大价值。
>
> #### 输入格式
>
> 第一行两个整数,$N,V$,用空格隔开,分别表示物品种数和背包容积。
>
> 接下来有 $N$ 行,每行两个整数 $v_i,w_i$,用空格隔开,分别表示第 $i$ 种物品的体积和价值。
>
> #### 输出格式
>
> 输出一个整数,表示最大价值。
>
> #### 数据范围
>
> $0<N,V≤1000$
> $0<v_i,w_i≤1000$
>
> #### 输入样例
>
> ```
> 4 5
> 1 2
> 2 4
> 3 4
> 4 5
> ```
>
> #### 输出样例:
>
> ```
> 10
> ```
```java
/**
* Description: 完全背包问题
* 求在前 i 个物品中,在不超过 j 容量的前提下的组合最大值
*/
public class Beg_Complete {
final static int N = 10010;
final static int[] v = new int[N], w = new int[N];
final static int[][] f = new int[N][N];
final static int[] g = new int[N];
public static void main(String[] args) {
int n = Tools.getInt(), m = Tools.getInt();
for (int i = 1; i <= n; i++) {
v[i] = Tools.getInt(); w[i] = Tools.getInt();
}
prime(n, m);
optimization(n, m);
ultimateOptimization(n, m);
}
private static void prime(int n, int m) {
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++)
for (int k = 0; k * v[i] <= j; k++)
f[i][j] = Math.max(f[i][j], f[i - 1][j - v[i] * k] + k * w[i]);
System.out.println(f[n][m]);
}
private static void optimization(int n, int m) {
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; j++) {
f[i][j] = f[i - 1][j];
if (v[i] <= j) f[i][j] = Math.max(f[i][j], f[i][j - v[i]] + w[i]);
}
System.out.println(f[n][m]);
}
private static void ultimateOptimization(int n, int m) {
for (int i = 1; i <= n; i++)
for (int j = v[i]; j <= m; j++) {
g[j] = Math.max(g[j], g[j - v[i]] + w[i]);
}
System.out.println(g[m]);
}
}
```
> **优化过程**
>
> 令 $j$ 等于 $x + k * v[i]$ 则有 $$f[i][j] = f[i][x + k * v[i]]$$
>
> 从题意出发,有 ① $$f[i][x + k * v[i]] = max(f[i - 1][x + k * v[i]], f[i - 1][x + (k - 1) * v[i]] + w[i], ···, f[i - 1][x] + k * w[i])$$
>
> 同理可得 ② $$f[i][x + (k - 1) * v[i]] = max(f[i - 1][x + (k - 1) * v[i]], f[i - 1][x + (k - 2) * v[i]] + w[i], ···, f[i - 1][x] + k * w[i])$$
>
> 那么整理可得 ③ $$f[i][x + k * v[i]] = max(f[i - 1][x + k * v[i]], f[i][x + (k - 1) * v[i]] + w[i])$$
>
> 故 $$f[i][j] = max(f[i - 1][j], f[i][j - v[i]] + w[i])$$
>
> 优化为一维,将 $i$ 去除,可得 ④ $$f[j] = max(f[j], f[j - v[i]] + w[i])$$
>
> 对比可知 $③ == ④$
>
> **对比**
>
> - 类似 01背包问题,但 01背包问题要求只选择一次,故只能由后向前遍历,将下一层的数据覆盖上一层的数据;
>
> - 完全背包问题则是要求选择多次,可能出现使用同一层的数据进行更新,故只需从前往后遍历,使之覆盖数据就行
**参考**
- [完全背包证明](https://www.acwing.com/blog/content/1917/)
- [算法基础课](https://www.acwing.com/activity/content/11/)

01背包、完全背包