蓝桥杯--DP1 AcWing 2. 01背包问题

本文详细介绍了动态规划中的01背包问题,包括问题定义、状态转移方程及代码实现。讨论了如何从二维DP数组简化为一维,以降低空间复杂度,并解释了简化过程中需要注意的遍历方向。最后,通过一个具体的01背包问题实例展示了简化后的代码运行逻辑。
摘要由CSDN通过智能技术生成

DP问题简单是真简单,难是真的难,刚学算法被dp折磨的一天做一道题都费劲,主要还是要学会分析题意,找出状态转移公式,不过y总的分析法一直也没怎么看懂,大部分时候还是看灵性

背包问题属于经典DP问题了,01背包算式背包中最简单的一种,一般会和其他知识点结合出现,贴道模板题

AcWing 2. 01背包问题

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。
接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

题意:也就是大多数01背包问题的统一题意,给出n个物品,每个物品带有一个重量v和价值w,再给出一个最大容量为m的背包,求在各种组合中能使背包中所放物品价值最大的值

dp问题的核心思想就是从前向后推出最终结果并记录,类似递推的思想,当随机选取一个状态–i个物品、j为最大容量时,可以判断当前大体上具有两种情况,一种是最大容量j不足以存放第i个物品,即j < v [ i ] ,当发生这种情况时,无论如何都不可能放进第i个物品,可以将其舍去,令(i,j)时的值等于(i-1,j)时的值。

另一种情况,当容量j足以存放第i件物品时,此时又分两种情况,选择或不选择:若不选择第i件物品,则与上述情况相同,(i,j)的值即是(i-1,j)的值。当选择时,就对容量和价值进行处理,由于存放了第i件物品,容量变为 j - v[ i ],此时情况变为,在前(i-1)个物品中,以j - v[ i ]的容量,再次获取最大值,因此此时(i,j)的值,变为(i-1,j- v[ i ])+w[ i ]。
在容量足够的两种情况中,不能保证放进 i 件物品就一定是最大值,因此对选与不选两种情况进行取max操作

最终状态方程即为
dp[ i ][ j ]=max(dp[ i-1 ][ j ], dp[ i-1 ][ j-v[ i ] ]+w[ i ]);

代码如下

import java.io.*;
import java.util.*;

public class Main {
	static Scanner tab = new Scanner(System.in);
	static BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	static BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(System.out));
	static int N = 1010;

	
	public static void main(String[] args) throws IOException {		
		int dp[][]=new int [N][N];//(物品数量,最大容量)
		int v[]=new int [N];
		int w[]=new int [N];
		int n=tab.nextInt();//数量
		int m=tab.nextInt();//容量
		for(int i=1;i<=n;i++) {
			v[i]=tab.nextInt();
			w[i]=tab.nextInt();
		}
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				if(j>=v[i])//最大容量能够装下第i件物品
					dp[i][j]=Math.max(dp[i-1][j], dp[i-1][j-v[i]]+w[i]);
				else
					dp[i][j]=dp[i-1][j];
			}
		}
		System.out.println(dp[n][m]);
		
	}
}

常规01背包代码就是这些,但当01背包与其他算法结合出现时,一个较大的二维dp数组可能会卡空间,需要简化空间复杂度。

另一方面观察状态方程,可以看到dp[i][j]的只与它的上一层dp[i][~]有关,因此可以将其简化成一维,dp[j]代表容量为 j 时的最大价值。

这里的简化思想可以参考前缀和的简化,一般来说,前缀和数组公式为s[i]=a[i]+s[i-1],但这里a[0]~a[i-1]的值完全是不使用的,可以直接将前缀和存储在a[i]中,用前缀和将原数组覆盖过去:a[i]=a[i-1]+a[i]

有点牵强,大体思想即是当考虑完第i-1个物品的各种情况后,后续的计算是无需使用0~(i-2)值的,因此直接将i-1的各值存在数组中,覆盖前有的数值,用于后续第i个物品的计算即可。而当第i个物品计算后,同理将i-1的值覆盖,继续第i+1物品的计算

但此题需要注意的一个点是,由于原代码是从小到大遍历,将dp数组简化一维后将其抽象为一个横向数组,在对第 i 个物品计算时,每次对dp[ j ]的计算都需要使用的是其左侧的值,如果从左至右遍历的话,当计算一个dp[ j ]的值时,需要使用的左侧值已经更新为第 i 层,而计算所需要的是第i-1层的数据,它已经被覆盖了,继续计算的话会使结果偏大,因此在简化代码中需从右向左遍历计算,即是从大容量从小容量遍历,来保证数据的准确

非常想贴个图上来,可惜没什么好画图的软件,建议在纸上模拟一遍简化代码的实现过程,再模拟一遍从左向右遍历的错误过程,比较容易理解

代码如下

for(int i=1;i<=n;i++) {
	for(int j=m;j>=v[i];j--) {//直接减至v[i]可以少个判断语句
		dp[j] = Math.max(dp[j], dp[j-v[i]] + w[i]);
	}
}

虽说是道入门题,可以说是入的相当费劲了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值