【AcWing】AcWing 2. 01背包问题

简介: 目录一、题目1、原题链接2、题目描述二、解题报告1、思路分析2、时间复杂度3、代码详解

一、题目

1、原题链接

2. 01背包问题 - AcWing题库

2、题目描述

有 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


二、解题报告

思路来源:AcWing 2. 01背包问题(闫氏DP分析法) - AcWing


y总yyds


1、思路分析

二维dp


1)创建dp数组,dp[i][j]代表容量为j的背包可以放物品1~i中的任意物品背包所能达到的最大价值。

2)dp[i][j]的求取是两种情况中的最大值,第一种情况是dp[i-1][j],代表不放第i件物品,下一次只从前i-1件物品中来选择物品放入;第二种情况是dp[i-1][j-v[i]]+w[i],代表放第i件物品,所以背包中就必须得有i物品的位置,所以背包容量得减去i物品的体积,同时背包的价值也要加上i物品的价值,因为本次已经选了i物品,所以下一次就不能再选i物品,所以i-1。我们选择这两种情况中的最大值,作为dp[i][j]的最大值。

所以,递推方程为:dp[i][j]=max(dp[i-1][j],dp[i-1] [j-v[i]]+w[i])

3)针对背包容量为0或物品为0时(也就是数组第一列和第一行),dp的数组的值初始化均为0,其他值都可通过递推式求得(通过它左上方某个元素和它正上方的元素通过递推式求得)。

4)两层for循环遍历物品和背包容量,遍历顺序怎样都可以,因为我们无论怎样遍历都可以通过递推方程算出相应的值。对dp数组赋值,最后输出dp[N][V],即为所求。


一维dp

1)在二维dp的情况下,我们可以优化一下空间,将二维数组压缩为一维数组,这个一维数组始终存储的是上一行的dp值,需要算本次的dp值,只要通过上一层的dp值也就是原数组值进行逐个更新即可。

2)思路与二维dp相同,所以递推方程也很类似:dp[j]=max(dp[j],dp[j-v[i]]+w[i]),仅仅是去掉了第一维的下标,将多个物品压缩在一行,每次更新某个元素,我们更新成了下一行该位置元素的值。

3)两层for循环分别遍历物品和背包,不可颠倒,而且背包容量需倒序遍历。因为我们将物品这一维给去掉了,所以第一次遍历数组中存放的是物品1在不同容量背包下所能达到的最大价值,我们需要遍历所有的物品在不同容量背包中所能带来的最大价值,而每次遍历物品都会依托于它的上一行,也就是它的上一个物品的所有dp值(对于二维dp来说就是上一行的值),如果先遍历背包的话,我们当前dp数组存储的不是上一行的dp值,而是一列值(就是在一定背包容量下,不同物品所带来的价值),这显然与我们预期不符。而遍历顺序由于在二维递推中,当前值取决于它左上方某个元素和它正上方的元素,而一维递推直接把它所在这一行和它上一行压缩在了同一行中,所以递推只取决于了它左边的元素,针对某一次更新dp数组,首先,我们可以知道现在未更新的dp数组存储的是上一个物品的所有情况的最大价值,我们需要在此基础上更新出我们该行的dp值,而且是根据左边元素进行更新,如果我们也从左边开始更新,先更新数组第一个元素,然后数组第一个元素的值已经更新成了新的值,接下来,第二个元素更新,这时候发现它的左边元素(也就是第一个元素)已经更新了,不是上一行的值了,而我们需要上一行的值来进行更新,所以就会发生错误,我们从右往左开始倒序遍历就不会出现这种情况(这就很类似数组的插入操作,我们插入了一个元素到数组中,我们需要从插入位置开始逐个更新数组值,我们也是从数组最后一个元素来操作,依次向后挪,如果从插入位置开始挪的话,后面的值就都被前面的元素值给覆盖掉了)。


4)对dp数组赋值,最后输出dp[V],即为所求。

2、时间复杂度

时间复杂度均为O(n^2)


3、代码详解

二维dp


#include <iostream>

using namespace std;

int dp[1010][1010];

int v[1010];

int w[1010];

int main()

{   int N,V;

   cin>>N>>V;

   //注意下标,物品编号从1开始,下标为0的物品代表什么也不放

for(int i=1;i<=N;i++){

 cin>>v[i]>>w[i];

}

//i从1开始,i从0开始会导致数字下标越界

for(int i=1;i<=N;i++){

 for(int j=0;j<=V;j++){

  //如果当前背包还能放下第i件物品才与不放i物品时背包的最大价值比较

    if(j-v[i]>=0)

  dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);

  //如果放不下物品i,就不放物品i

    else

      dp[i][j]=dp[i-1][j];

 }

}

   cout<<dp[N][V];

return 0;

}


一维dp


#include <iostream>

using namespace std;

int dp[1010];

int v[1010];

int w[1010];

int main()

{   int N,V;

   cin>>N>>V;

   //注意下标,物品编号从1开始,下标为0的物品代表什么也不放

for(int i=1;i<=N;i++){

 cin>>v[i]>>w[i];

}

//i从1开始,i从0开始会导致数字下标越界

for(int i=1;i<=N;i++){

 for(int j=V;j>=0;j--){

  //如果当前背包还能放下第i件物品才与不放i物品时背包的最大价值比较

    if(j-v[i]>=0)

  dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

  //如果放不下物品i,就不放物品i

    else

      dp[j]=dp[j];

 }

}

   cout<<dp[V];

return 0;

}


在上面代码的基础上可以再简化一下代码,得到最终优化版代码如下:


#include <iostream>

using namespace std;

int dp[1010];

int v[1010];

int w[1010];

int main()

{   int N,V;

   cin>>N>>V;

for(int i=1;i<=N;i++){

 cin>>v[i]>>w[i];

}

for(int i=1;i<=N;i++){

 for(int j=V;j>=v[i];j--){

  dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

 }

}

   cout<<dp[V];

return 0;

}

目录
相关文章
|
1月前
acwing 1010 拦截导弹
acwing 1010 拦截导弹
30 1
|
1月前
acwing 1107 魔板
acwing 1107 魔板
10 0
|
1月前
acwing 1116 马走日
acwing 1116 马走日
10 0
|
1月前
acwing 188 武士风度的牛
acwing 188 武士风度的牛
8 0
|
1月前
acwing 285. 没有上司的舞会
acwing 285. 没有上司的舞会
15 0
|
1月前
|
人工智能
AcWing 274. 移动服务(线性dp)
AcWing 274. 移动服务(线性dp)
14 0
|
1月前
acwing 1014 登山
acwing 1014 登山
24 0
|
1月前
acwing 1017 怪盗基德的滑翔翼
acwing 1017 怪盗基德的滑翔翼
27 0
|
1月前
acwing 1012 友好城市
acwing 1012 友好城市
15 0
|
6月前
|
人工智能
acwing 5478. 分班
acwing 5478. 分班