acwing背包问题学习笔记,活动连接
一、01背包问题
从集合的角度分析dp问题如下:
代码:
import java.io.*;
import java.util.*;
public class Main{
public static int[] ve = new int[1010];
public static int[] w = new int[1010];
public static int[][] dp = new int[1010][1010];
public static void main(String[] strs) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] nv = in.readLine().split(" ");
int n = Integer.parseInt(nv[0]),v = Integer.parseInt(nv[1]);
for(int i = 1;i<=n;i++){
String[] temp = in.readLine().split(" ");
ve[i] = Integer.parseInt(temp[0]);
w[i] = Integer.parseInt(temp[1]);
}
//dp[i,j] = max(dp[i,j],dp[i-1,j],dp[i-1,j-v]+w)
for(int i = 1;i<=n;i++){
for(int j = 0;j<=v;j++){
dp[i][j] = dp[i][j]>dp[i-1][j]?dp[i][j]:dp[i-1][j];
if(j>=ve[i]) dp[i][j] = dp[i][j]>dp[i-1][j-ve[i]]+w[i]?dp[i][j]:dp[i-1][j-ve[i]]+w[i];
}
}
System.out.print(dp[n][v]);
}
}
优化成一维,注意到以下两个事实:
- 更新dp[i]时只用到了dp[i-1]层的数
- 更新dp[j]时只用到了dp[j-v]
故可以先尝试直接把第一维删掉
for(int i = 1;i<=n;i++){
for(int j = ve[i];j<=v;j++){
//dp[j] = dp[j]>dp[j]?dp[j]:dp[j];
dp[j] = dp[j]>dp[j-ve[i]]+w[i]?dp[j]:dp[j-ve[i]]+w[i];
}
}
我们来检查一下这样的写法
- dp[i,j]=dp[i-1,j]没问题
- dp[i-1][j-ve[i]]是否等于dp[j-ve[i]]? 由于我们循环更新的过程是从左往右更新,所以此时的dp[j-ve[i]]已经被更新过了,但是我们需要用的是上一层没有更新过的dp[j-ve[i]]。要解决这个问题,只需要从后往前遍历更新即可。
import java.io.*;
import java.util.*;
public class Main{
public static int[] ve = new int[1010];
public static int[] w = new int[1010];
public static int[] dp = new int[1010];
public static void main(String[] strs) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] nv = in.readLine().split(" ");
int n = Integer.parseInt(nv[0]),v = Integer.parseInt(nv[1]);
for(int i = 1;i<=n;i++){
String[] temp = in.readLine().split(" ");
ve[i] = Integer.parseInt(temp[0]);
w[i] = Integer.parseInt(temp[1]);
}
//dp[i,j] = max(dp[i,j],dp[i-1,j],dp[i-1,j-v]+w)
for(int i = 1;i<=n;i++){
for(int j = v;j>=ve[i];j--){
dp[j] = dp[j]>dp[j-ve[i]]+w[i]?dp[j]:dp[j-ve[i]]+w[i];
}
}
System.out.print(dp[v]);
}
}
二、完全背包问题
完全背包问题和01背包问题的区别仅仅在于每种物品可以使用无限次。仍然在集合的角度分析。
其实可以看出01背包问题只是完全背包问题的一个k<=1的特例,代码:
import java.io.*;
import java.util.*;
import java.math.*;
public class Main{
public static int[] ve = new int[1010];
public static int[] w = new int[1010];
public static int[][] dp = new int[1010][1010];
public static void main(String[] strs) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] nv = in.readLine().split(" ");
int n = Integer.parseInt(nv[0]),v = Integer.parseInt(nv[1]);
for(int i = 1;i<=n;i++){
String[] temp = in.readLine().split(" ");
ve[i] = Integer.parseInt(temp[0]);
w[i] = Integer.parseInt(temp[1]);
}
//dp[i,j] = max(dp[i,j],dp[i-1,j],dp[i-1,j-v]+w)
for(int i = 1;i<=n;i++){
for(int j = 0;j<=v;j++){
for(int k = 0;k*ve[i]<=j;k++){
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-k*ve[i]]+w[i]);
}
}
}
System.out.print(dp[n][v]);
}
}
如何优化:
当我们把状态转移方程写出来时,我们可以得到递推式:
- f[i,j] = max(f[i-1,j],f[i,j-v]+w)
这样我们就可以继续利用01背包问题中的降维方法优化了。
首先优化状态转移方程:
for(int i = 1;i<=n;i++){
for(int j = 0;j<=v;j++){
dp[i][j] = dp[i-1][j];
if(j>=ve[i]) dp[i][j] = Math.max(dp[i][j],dp[i][j-ve[i]]+w[i]);
}
}
然后消去其中的第一维:
for(int i = 1;i<=n;i++){
for(int j = ve[i];j<=v;j++){
dp[j] = Math.max(dp[j],dp[j-ve[i]]+w[i]);
}
}
由于这里用到的是dp[i][j-ve[i]],所以不存在01背包问题中需要从后往前遍历的问题。
我们再来关注一下优化后状态转移方程:
- f[i,j] = max(f[i-1,j],f[i,j-v]+w)
他也能用集合划分的观点来解释:
- 不加第i个物品时,显然f[i,j] = f[i-1,j]
- 如果加了第i个物品,那么至少加一个,即f[i,j-v]+w
三、多重背包问题
弄懂完全背包问题后,多重背包问题就很简单了,只需要在枚举第i个物品加入的个数时判断是否大于Si即可。
import java.io.*;
import java.util.*;
import java.math.*;
public class Main{
public static int[] ve = new int[110],w = new int[110],s = new int[110];
public static int[][] dp = new int[110][110];
public static void main(String[] args) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] nv = in.readLine().split(" ");
int n = Integer.parseInt(nv[0]),v = Integer.parseInt(nv[1]);
for(int i = 1;i<=n;i++){
String[] temp = in.readLine().split(" ");
ve[i] = Integer.parseInt(temp[0]);
w[i] = Integer.parseInt(temp[1]);
s[i] = Integer.parseInt(temp[2]);
}
for(int i = 1;i<=n;i++){
for(int j = 0;j<=v;j++){
for(int k = 0;k<=s[i]&&j-k*ve[i]>=0;k++){
dp[i][j] = Math.max(dp[i][j],dp[i-1][j-k*ve[i]]+w[i]*k);
}
}
}
System.out.print(dp[n][v]);
}
}
四、分组背包问题
本质上还是一个完全背包问题,由于每组物品只能选一个加入背包,所以前i个物品和前i组物品没什么区别,在枚举第i个物品时,将第i组物品中的所有物品全部枚举一遍即可。
import java.io.*;
import java.util.*;
import java.math.*;
public class Main{
public static Map<Integer,int[][]> map = new HashMap<>();
public static int[][] dp = new int[110][110];
public static void main(String[] args) throws IOException{
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
String[] nv = in.readLine().split(" ");
int n = Integer.parseInt(nv[0]),v = Integer.parseInt(nv[1]);
for(int i = 1;i<=n;i++){
int s = Integer.parseInt(in.readLine());
int[][] temp = new int[s][2];
for(int j = 0;j<s;j++){
String[] vw = in.readLine().split(" ");
temp[j][0] = Integer.parseInt(vw[0]);
temp[j][1] = Integer.parseInt(vw[1]);
}
map.put(i,temp);
}
for(int i = 1;i<=n;i++){
for(int j = 0;j<=v;j++){
int[][] temp = map.get(i);//第i组背包
dp[i][j] = dp[i-1][j];
for(int k = 0;k<temp.length;k++){
if(j-temp[k][0]>=0) dp[i][j] = Math.max(dp[i][j],dp[i-1][j-temp[k][0]]+temp[k][1]);
}
}
}
System.out.print(dp[n][v]);
}
}