动态规划0、1背包问题,
在背包问题中,我们需要在固定的背包容量中,尽可能的装入价值更大的物品,对于某件物品的选择我们需要考虑它是选择装入背包还是不装。所以,我们称此类问题为0、1背包。
在此类背包问题中,要理解三个要素,其一是背包总重量C,其二是物品数量N,其三是物品的价w[]值。
按照动态规划的思路:将大的问题转换成小的问题求解。
我们的大问题是将“对于N件物品,在总重不超过C的前提下,求出最大总价值”
把大问题转换为小问题:对于当前物品,我是选还是不选?在两个选择中选出得到价值最大的存入表中。
选: 那么我们需要在接下来的N-1件物品中选出,总重量不超过C - v[i] (当前物品重量),那么此时当前物品的重量得到的最大价值是:当前物品的价值 + C-v[i] 重量时的最大价值。
不选: 那么我们需要在接下来的N-1件物品中选出,总重量不超过C的最大价值物品,那么当前的最大价值就是:0
可以看出,这里的最大价值是将0-C每一个重量级所获得的最大价值都求出来,在使用是直接拿出来(高级打表),这也符合动态规划的思想(不做多余的事),那么当我们选择当前重量的物品时,所获得的最大价值,就是 当前重量物品的价值 加上 背包总重量 减去 当前物品重量所得重量的最大价值之和
接下来结合题目做具体说明:
采药(P1048)
题目描述
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
输入格式
第一行有 2 个整数 T(≤ T ≤ 1000)和 M(1 ≤ M ≤ 100),用一个空格隔开,T 代表总共能够用来采药的时间,M 代表山洞里的草药的数目。
接下来的 M 行每行包括两个在 1到 100 之间(包括 1和 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出格式
输出在规定的时间内可以采到的草药的最大总价值。
输入输出样例
输入
70 3
71 100
69 1
1 2
输出
3
题目分析
其实已经不用分析了,通过前面的铺垫动动手指都知道这是背包问题。
那么既然知道是背包问题,那么就拿出套路:找状态转移方程,各个数据之间的关系。
题目中 ,T表示总时间(总容量),M表示草药的数量(物品数量),接下来的每一组数据,包含当前草药需要的时间v[ ] 和价值w [ ]。
对于一个某一种草药,是摘还是不摘?草药的时间很有可能是大于规定时间的,所以遇到采药时间>总时间时直接放弃。如果摘取M[i] 草药,那么接下来的M-1数量中,只能在剩余时间T-v[i] 中采到价值最大的草药。我们用i表示第i棵草药,j表示草药的时间 构建一个二维数组dp[ ][ ] ,dp[i][j]表示第i课草药价值为j的最大价值。对于摘还是不摘,在前面已经提到,不摘那么它的最大价值就是本身dp[i]/[j],摘那么就是需要在dp[i-1][j-v[i]]+w[i];中选出最大价值。可得状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]]+w[i])
举例:给出数据结合使用
5(总时间)
3(数量)
2 5
3 8
1 3
当第i件物品时间为j的最大价值时多少。
当只有1件物品时,时间为j时选、还是不选,他的价值最大;
j为时间,i为数量,价值请参照上面数据
数量 /时间 | j=0 | j=1 | j=2 | j=3 | j=4 | j=5 |
---|---|---|---|---|---|---|
i=0 | 0 | 0 | 0 | 0 | 0 | 0 |
i=1 | 0 | 0 | 5 | 5 | 5 | 5 |
i=2 | 0 | 0 | 5 | 8 | 13 | 13 |
i=3 | 0 | 3 | 5 | 8 | 13 | 13 |
注意:当物品数量为0时和时间为0时,我的最大价值都是0,前者我没有选择的物品,后者我没办法装。
当i=1时,一定是当时间足够时我才会选,也就是从时间j=v[1]时,因为这个物品我只选一次,那么从v[ 1 ]到T他的价值都是w[ 1 ]。
当i=2时,同样是需要时间足够才会选,所以,去计算时同样时从v[ 2 ]开始填,我的最大时间,能否满足(T>=v[ 2 ]),当前最大价值,应该是在当前时间-当前物品需要时间 的时间,最大价值+当前物品最大价值 ,这句话需要结合数据在表中进行理解。可以得到的是dp[i-1][j-v[[i]]+w[i]
如果不选,那么得到的最大价值就是dp[i][j]当前时间的最大价值。
以上,结合代码:
//i为数量,j为时间
#include<iostream>
using namespace std;
const long long M=1005;
long long t,n;
long long w[10001],v[M],dp[M][M];
int main(){
cin>>t>>n;
for(int i=1;i<=n;i++){
scanf("%i%i",&v[i],&w[i]);
}
for(int i=1;i<=n;i++){
for(int j=t;j>=0;j--){//从后往前填表
if(j>=v[i]) //当前的时间小于或等于总时间时才做选择否则为上一个(数量)的最大值
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);//这个转移方程请参照图标想象模拟理解
else dp[i][j]=dp[i-1][j];
}
}
/* for(int i=1;i<=n;i++){
for(int j=1;j<=t;j++){
cout<<dp[i][j]<<" ";
}
cout<<endl;
}*/
cout<<dp[n][t];
return 0;
}
优化为一维:
//一维数组解法
#include<iostream>
using namespace std;
int t,n;//t表示总时间,n表示总数量
int w[101],v[1001],dp[1001];//w表示每一株的价值,v表示每一株需要的时间,dp表示每一个时间的最大价值
int main(){
cin>>t>>n;
for(int i=1;i<=n;i++){
scanf("%i%i",&v[i],&w[i]);
}
for(int i=1;i<=n;i++){
for(int j=t;j>=v[i];j--){//从后面往前面填
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);//找出不选,选那个价值最大
}
}
cout<<dp[t];//输出最终的值
return 0;
}
后记:
在面对0、1背包问题时,主要问题就是选还是不选的问题,其实就是将每一个时间(重量),都求出最大的,在需要的时候直接提取出来可以使用了,当前面的n-1个时间(重量)都求出来了,对于n的最大时间(重量)就简单了。如何求,在考虑这个问题时,我们可以先把问题尽量缩小(类似递推、递推思想),先来考虑一下,当总时间为0,总容量为0时的最大价值,在来考虑他们分别为1时,的最大价值,逐渐往后推,在找出状态转移方程。
有问题请私信qaq
感谢阅读~