一文详解背包问题

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]);
        
    }
}

猜你喜欢

转载自blog.csdn.net/qq_45733304/article/details/125845816
今日推荐