一文详解背包问题

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]);
        
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值