0-1背包问题
0-1背包问题
算法思路
我的核心问题:怎么想到用二维数组dp[i][j]表示的?我只能想到用一维的dp[i],但是很不好实现
答:
- 0-1背包问题属于“决策”+“有限资源(背包容量)”的模式。
- 之所以用二维数组 dp[i][j],是因为 0-1 背包问题需要同时考虑 “物品数量” 和 “背包容量” 两个维度,
这两个维度都会影响我们的决策,所以dp数组自然是二维的。- 这个理解是初步的,等以后DP的题目做多了再来加深理解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int main()
{
int n, m;
cin >> n >> m; // 输入物品数量n和背包容量m
// 输入每个物品的体积和价值
for(int i = 1; i <= n; i++)
cin >> v[i] >> w[i];
// 动态规划主体部分
for(int i = 1; i <= n; i++)
for(int j = 1; j <= m; j++)
{
// 如果当前背包容量j小于第i个物品的体积v[i],则无法放入该物品
// 此时f[i][j]等于只考虑前i-1个物品时的最大价值
if(j < v[i])
f[i][j] = f[i - 1][j];
// 如果能放入,则需要决策是否选择第i个物品
// 不选择:f[i-1][j],即只考虑前i-1个物品
// 选择:f[i-1][j-v[i]] + w[i],即放入第i个物品,加上剩余容量能放的最大价值
// 取两种情况的较大值
else
f[i][j] = max(f[i - 1][j], f[i - 1][j - v[i]] + w[i]);
}
// 输出最终结果,即考虑所有n个物品,背包容量为m时的最大价值
cout << f[n][m] << endl;
return 0;
}
dp[i][j]
的值 = 从[0…i]这些物品中==任取==物品,放进容量为j的背包里第二层
for
循环很重要,要反复体会,这是解决下面这个问题的核心:Q: 如果有A B C三个物品,背包容量只能装其中2个,而选BC比AC好,但是在第一步的时候选A一定比不选A的总价值要大(因为第一步的时候只能看到A这一个物品),那我们在遍历到物品C的时候,怎么知道选C比选A好?又怎么能实现用C替换A的操作?
下面解释一下第二层
for(int j = 1; j <= m; j++)
循环如何解决我们的这个疑问:这个循环实现了”替换”的机制,解释如下:
当我们处理到物品C时,f[i-1][j]代表了”不选C”的最优解,可能包含了A
而f[i-1][j-v[i]]+w[i]代表了”选择C”的最优解,它基于”容量减去C后的最优解”再加上C的价值关键点是:当我们处理到物品C时,f[i-1][j-v[i]]已经记录了在剩余容量下的最优解。如果容量有限且BC组合确实比AC更优,那么在处理B时,可能已经将A的选择状态”隐式地替换”为了不选A。
举例说明:
- 假设背包容量为2,A、B、C的体积都是1
- 假设A的价值是10,B的价值是15,C的价值是20
- 处理完A后,f[1][1]=10
- 处理B时,对于j=1,f[2][1]=max(f[1][1], f[1][0]+w[2])=max(10, 0+15)=15(已经用B替换了A)
- 对于j=2,f[2][2]=max(f[1][2], f[1][1]+w[2])=max(0, 10+15)=25(选A和B)
- 处理C时,对于j=1,f[3][1]=max(f[2][1], f[2][0]+w[3])=max(15, 0+20)=20(用C替换B)
- 对于j=2,f[3][2]=max(f[2][2], f[2][1]+w[3])=max(25, 15+20)=35(选B和C)
所以最终选择了BC而不是AC,即使一开始A看起来更好。这就是DP的优势 - 它不是贪心地固定早期决策,而是在考虑后续物品时,能够重新评估并调整之前的选择。
实质上,f[i][j]在每一步都保存了”在当前条件下的最优解”,使得后续物品可以基于这些最优子结构进行决策,从而实现了隐式的”替换”机制。
我的代码(细节纠错)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import java.util.Scanner //java小写,只有类名要大写
import java.util.Math //不用导入
public Class Main{
public static void main(String args[]){
Scanner in=new Scanner(""); //Scanner(System.in)
int N,V;
N=in.nextInt();
V=in.nextInt();
int[N] v[]; //数组声明的语法错了
int[N] w[]
for(int i=0;i<N;i++){
w[i]=in.nextInt();
v[i]=in.nextInt();
}
int[N][N]dp; //数组大小声明得不对
for(int i=0;i<N;i++){
for(int j=0;j<N;j++){ //j<=V而不是j<N
if(j<w[i]){
dp[i][j]=dp[i-1][j];
}
dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]); //换成Math.max
}
}
System.out.println(dp[N][V]);
}
}
- 语法错误:
- Class 应该小写为 class(Java区分大小写)
- import java.util.Math 应该改为 java.lang.Math(不过Math类其实会自动导入)
- 数组声明语法错误:
int[N] v[]
应该改为int[] v = new int[N]
- Scanner初始化错误:new Scanner(“”) 应该改为 new Scanner(System.in)
- 逻辑错误:
- DP数组大小不对,应该是
[N+1][V+1]
(要考虑从0开始的情况) - 内层循环应该是 j <= V,不是 j < N(要遍历所有可能的背包容量)
- 缺少DP数组的初始化(边界条件)
- 使用 max() 应该改为 Math.max()
- DP数组大小不对,应该是
This post is licensed under CC BY 4.0 by the author.