Post

0-1背包问题

0-1背包问题

算法思路


我的核心问题:怎么想到用二维数组dp[i][j]表示的?我只能想到用一维的dp[i],但是很不好实现

答:

  • 0-1背包问题属于“决策”+“有限资源(背包容量)”的模式。
  • 之所以用二维数组 dp[i][j],是因为 0-1 背包问题需要同时考虑 “物品数量” 和 “背包容量” 两个维度,
    这两个维度都会影响我们的决策,所以dp数组自然是二维的。
  • 这个理解是初步的,等以后DP的题目做多了再来加深理解

Karl

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;
}
  1. dp[i][j]的值 = 从[0…i]这些物品中==任取==物品,放进容量为j的背包里

  2. 第二层for循环很重要,要反复体会,这是解决下面这个问题的核心:

    Q: 如果有A B C三个物品,背包容量只能装其中2个,而选BC比AC好,但是在第一步的时候选A一定比不选A的总价值要大(因为第一步的时候只能看到A这一个物品),那我们在遍历到物品C的时候,怎么知道选C比选A好?又怎么能实现用C替换A的操作?

  3. 下面解释一下第二层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]);
  }
}
  1. 语法错误:
    • 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)
  2. 逻辑错误:
    • DP数组大小不对,应该是 [N+1][V+1](要考虑从0开始的情况)
    • 内层循环应该是 j <= V,不是 j < N(要遍历所有可能的背包容量)
    • 缺少DP数组的初始化(边界条件)
    • 使用 max() 应该改为 Math.max()
This post is licensed under CC BY 4.0 by the author.