目录
一、01背包
1. 01背包的空间优化
import java.util.Scanner;
public class Knapsack_pro {
/*
01背包内存优化
原理是这样
如果我计算完dp[i],那么dp[i-1]就没什么用了,最后我的结果也只是在
dp[n]行里面找到值
当我计算完dp[i]行的时候第i-1行无用,剩下的是一个
一维的数组
所以我就可以,将dp二维数组直接变为一维数组
dp继承的是自己的上一个dp[i-1][j],和[i-1][j-w[i]]的下标
映射到一维数组中,取的就是自己这个位置上,和j-w[i]
这样只需要一个一维数组,就可以解决问题
需要注意的是,从i-1行更新到i的时候,需要重后往前遍历
因为更新后面的值需要用到前面的值
而且dp[0]也不用更新了,dp[0]一定是上一个的值
j的循环范围应该是最大容量到自己本身的容量
*/
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
int N = scan.nextInt();
int V = scan.nextInt();
int[] w = new int[N+1];
int[] v = new int[N+1];
for (int i = 1; i <= N; i++) {
w[i] = scan.nextInt();
v[i] = scan.nextInt();
}
int[] dp = new int[V+1];
for(int i=1;i<=N;i++){
for(int j = V;j>=w[i];j--){
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
}
}
System.out.print(dp[V]);
scan.close();
}
/*
模板
首先
N表示物品数量
V表示背包容量
w表示物品体积
v表示物品价值
01背包的优化版本和完全背包的优化版本很相似
它的状态转移方程和完全背包一致
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i])
只不过01背包用的是上一行的dp[j-w[i]],完全背包用的是这一行
所以完全背包是要用优化后的前面的数字
而01背包要用的是优化前的前面的数字
所以完全背包从前往后遍历,01背包从后往前遍历
i从1到N
j从V到w[i] (后面的没必要改)
int N;
int V;
int[] w = new int[N+1];
int[] v = new int[N+1];
int[] dp = new int[N+1];
for(int i= 1;i<=N;i++){
for(int j=V;j>=w[i];j--){
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
}
}
*/
}
二、完全背包
1. 完全背包的时间优化
/*
完全背包时间优化版本
上一个状态转移方程dp = Math.max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]
展开
dp[i][j] =
|- dp[i-1][j]
max| dp[i-1][j-w[i]]+v[i]
| dp[i-1][j-w[i]*2]+v[i]*2
| …………
|- dp[i-1][j-k*w[i]] + v[i]*k
把j-w[i]代入j
dp[i][j-w[i]] +v[i]
|- dp[i-1][j-w[i]] + v[i]
max| dp[i-1][j-w[i]*2]+v[i]*2
| …………
|- dp[i-1][j-k*w[i]] + v[i]*k
所以dp[i][j] = Math.max(dp[i-1][j],dp[i][j-w[i]]+v[i]) 区别在于01背包的方程是dp[i-1][j-w[i]]+v[i] 只能继承头上那个
而完全背包的 方程是 dp[i][j-w[i]]+v[i] 可以继承自己这一行的背包
这两种状态,其实是一种都不放,和至少放一个
可以继承自己这一行的原因是,就算再放一个第i种物品,那也是i种,但是01背包放完之后就没有了,所以继承不了自己这一行
*/
/*
核心代码
KnapsackComplete
初始化dp[0][0]
for i(1,n)
for j(0,c[i]-1)
dp[i][j] = dp[i-1][j] //容量不足以再放进一个i物品,所以只能继承上一个
for j(c[i],m)
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-c[i]]+w[i]) //这样后面的都可以继承同一行的前面的,接龙继承k次就算是规避掉循环了
*/
/*
// for(int i= 1;i<=N;i++){
// for(int j = 0;j<=V;j++){
// for(int k=0;k<=j/w[i];k++){
// dp[i][j] = Math.max(dp[i][j],dp[i-1][j-k*w[i]]+k*v[i]);
// }
// }
// }
for(int i= 1;i<=N;i++){
for(int j = 0;j<w[i];j++){
dp[i][j] = dp[i-1][j];
}
for(int j=w[i];j<=V;j++){
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-w[i]]+v[i]);
}
}
*/
2. 完全背包的空间优化
/*
完全背包空间优化
上上一行的数据永远就没有用了,因为怎么继承都用不到他
我们只关注上一行和这一行
其实完全背包时间优化完成后,状态转移方程就已经和01背包很像了
所以空间优化也很好想
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]) 此时的状态转移方程就和01背包完全一样了
只不过,01背包优化后面的值需要用到上一行的前面的值,所以并不能一上来就把这一行的前面优化掉,这样上一行的数据就没有了
完全背包用的是上一行的自己,还有这一行的j-w[i],所以完全背包就是要用优化完的前面的值
所有01背包只能从后往前遍历
完全背包可以从前往后遍历
*/
/*
模板
首先
N表示物品数量
V表示背包容量
w表示物品体积 大小是N+1
v表示物品价值 大小是N+1
就是上面这个
i从物品的1到n
j从第一个物品的重量开始向后继承,一直到继承到背包的容量
dp[j]每次接龙前面的dp[j-w[i]]+v[i],和自己本身比较dp[j]也就是上一行的dp[i-1][j]只不过空间优化了
int N;
int V;
int[] w = new int[N+1];
int[] v = new int[N+1];
int[] dp = new int[V+1];
for(int i= 1;i<=N;i++){
for(int j=w[i];j<=V;j++){
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
}
}
总结一下就是
dp[j] = Math.max(dp[j],dp[j-w[i]]+v[i]);
i从1到N
01背包j从V到w[i]
完全背包j从w[i]到V
*/