目录
一、前言
对于学计算机的同学来说,学习算法是一件非常重要的事情,废话不多讲,我们来讲讲“01背包问题”。
二、01背包
何谓01背包,对于每个要放入背包的物品,只需要考虑放与不放两种选择。如果不选择将其放入背包中,则不需要处理,视为0,反之视为1。
1、问题描述
有N件物品和一个容量为V的背包。放入第 i 件物品耗费的费用(体积)是 Ci,得到的价值是 Wi。求:将哪些物品装入背包可使价值总和最大。
2、基本思路
01背包是最基础的背包问题,特点是:每种物品只有一件,可以选择放或者不放。
我们第一反应要想到用子问题来定义状态:即 dp[i][v] 表示前 i 件物品放入一个容量为 v 的背包中可以获得的最大价值。其状态转移方程为:
dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i]);
该方程理解起来非常简单,来解释一下,实际上直接看方程也能大致知道想表达什么。
“将前 i 件物品放入容量为 v 的背包中”这个子问题,若只考虑第 i 件物品的策略(放或不放),那么就可以转化为一个只和前 i - 1 件物品相关的问题。如果不放第 i 件物品,那么问题就转化为“前 i - 1 件物品放入容量为 v 的背包中”,价值为 dp[i- 1][v];如果放第 i 件物品,那么问题就转化为“前 i − 1 件物品放入剩下的容量为 v − Ci 的背包中”,此时能获得的最大价值就是 dp[i − 1][v − Ci] 再加上通过放入第 i 件物品获得的价值 Wi。
据说这个方程非常重要,基本上所有跟背包相关的问题的方程都可以由它衍生出来。
最后稍微思考一下可得到递推式逻辑:
(不能装)如果 当前背包容量V[i] < 待装物品体积C[i] ,则 dp[i][v]=dp[i-1][v]
(能装,决策装不装)否则 dp[i][v] = max(dp[i - 1][v], dp[i - 1][v - c[i]] + w[i])
补充:动态规划法与分治法的区别
动态规划与分治法类似,都是把大问题拆分成小问题,通过寻找大问题与小问题的递推关系,解决一个个小问题,最终达到解决原问题的效果。但不同的是,分治法在子问题和子子问题等上被重复计算了很多次,而动态规划则具有记忆性,通过填写表把所有已经解决的子问题答案记录下来,在新问题里需要用到的子问题可以直接提取,避免了重复计算,从而节约了时间,所以在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。
3、优化空间复杂度
本文着重让读者明白何谓01背包以及解01背包的基本算法思路,将空间二维优化为空间一维需要更好的基础,写了可能读者在比赛或机考时不一定能用得上,综合考虑后决定还是不写,感兴趣的兄弟可以找其他博客阅读阅读,或者到下面例题的题解里面搜寻想要的解释。
三、题例
1、上链接
2、题目分析
这是一道模板题,直接套模板即可。
3、代码
(1)C++
#include <iostream>
using namespace std;
const int MAXN=1003;
int V[MAXN]; //每个背包的体积
int W[MAXN]; //每个背包的价值
int dp[MAXN][MAXN]; //dp[i][j], j体积下前i个物品的最大价值
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=1;j<=v;++j){ //为了填表中的每一行,得到在背包任意体积下的最优解,填表见下图
if(j<V[i])
dp[i][j]=dp[i-1][j];
else
dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i]);
}
}
cout<<dp[n][v]<<endl;
return 0;
}
/* 补充:
* 当前的状态依赖于之前的状态,我们可以默认为从初始状态(边界) dp[0][j] = dp[i][0] = 0开始决策,
* 有 N 件物品,则需要 N 次决策,每一次对第 i 件物品的决策,状态dp[i][j]不断由之前的状态更新而来。
* 显然,当只有 0 件物品的时候,或者背包最大容量为 0 的时候, dp[0][j] = dp[i][0] = 0
* */
(2)python——二维朴素写法
MAXN=1003
dp=[[0]*MAXN for i in range(MAXN+1)] #注意创建二维数组是这样子
V=[0]*MAXN
W=[0]*MAXN
# test=[[0]*MAXN]*MAXN #这样创数组有错误
# print(test[10][10])
n,v=map(int,input().split())
for i in range(1,n+1):
V[i],W[i]=map(int,input().split())
for i in range(1,n+1):
for j in range(1,v+1):
if j<V[i]:
dp[i][j]=dp[i-1][j]
else:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-V[i]]+W[i])
print(dp[n][v])
(3)python——一维空间优化写法
MAXN=1003
dp=[0]*MAXN
V=[0]*MAXN
W=[0]*MAXN
n,v=map(int,input().split())
for i in range(1,n+1):
V[i],W[i]=map(int,input().split())
for i in range(1,n+1):
for j in range(v,0,-1):
if j<V[i]:
dp[j]=dp[j]
else:
dp[j]=max(dp[j],dp[j-V[i]]+W[i])
print(dp[v])
# dp=[0,1,2,3,4,5,6,7,8,9,10,11,21]
# for i in range(12,-1,-1): #这样是逆序输出全部元素
# print(dp[i],end=" ")
一维为何要这样写呢?先上一张图。
懂的人自然懂,不懂的人仔细思考一下也会懂~
以上,01背包,更多内容可到Acwing讨论区学习。
祝好,加油!