我一直觉得我对背包问题其实是会的,知道今天看到一道背包题发现两眼迷茫…
所以写一写关于背包问题的总结,复习一下
首先是最基础的 0 / 1背包问题
上题目:
辰辰是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间,每一株也有它自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。”
如果你是辰辰,你能完成这个任务吗?
第一行有 2 2 2 个整数 T T T( 1 ≤ T ≤ 1000 1 \le T \le 1000 1≤T≤1000)和 M M M( 1 ≤ M ≤ 100 1 \le M \le 100 1≤M≤100),用一个空格隔开, T T T 代表总共能够用来采药的时间, M M M 代表山洞里的草药的数目。
接下来的 M M M 行每行包括两个在 1 1 1 到 100 100 100 之间(包括 1 1 1 和 100 100 100)的整数,分别表示采摘某株草药的时间和这株草药的价值。
输出在规定的时间内可以采到的草药的最大总价值。
70 3
71 100
69 1
1 2
3
【数据范围】
【题目来源】
NOIP 2005 普及组第三题
所谓0/1背包就是对于一个物体,要么取,要么不去,就对应着二进制中的 0 和 1
考虑问题所涉及的变量,分别有这几个可变因素
1.我总共的时间
2.某个物品所需要的时间
3.某个物品所拥有的价值
我们直接考虑中间过程,对于第 i 个物品 , 假设他的价值为 vi,,所需要的时间为 t i他有两个选择
1.拿:
那么此时他能获得的价值是背包容量减去i 自身所需容量情况下的最优解加上i的价值
2.不拿
那么此时能获得的最大价值为当前背包容量所能获得的最大值
又因为背包本身随着物品的拿或者不拿在动态的变化着,所以dp方程中有一个维度要给背包的容量
而物品是在不断选择的,所以有一个维度要来判断选择的物品编号
所以 dp[i] [j] 代表着在背包容量为i的情况下1~j个物品所可以达到的最大值
那么转移方程可以写为
dp[ i ] [ j ] = max(dp[ i - weight[ j ] ] [ j - 1 ] + value[ j ] , dp[ i ] [ j - 1])
(weight[ j ] , value[ j ]分别为物品 j 所需的容量以及价值)
同时
背包的容量可以为0 , 所选的物品也可以为0
所以边界条件为dp[0] [ ] = dp[ ] [0] = 0;
所以代码为:
#include
using namespace std;
int main()
{
int total_time , total_number;
cin >> total_time >>total_number;
int time[total_number]={0} , value[total_number] = {0};
for(int temp = 0 ; temp < total_number ; temp++)
cin >> time[temp] >> value[temp];
int dp[total_time + 1][total_number + 1] ;
for(int temp = 0 ; temp < total_number+1 ; temp++) //给两个边界先提前赋值为0
dp[0][temp] = 0;
for(int temp = 0 ; temp < total_time+1 ; temp++)
dp[temp][0] = 0;
for(int temp1 = 1 ; temp1 < total_time + 1 ; temp1++){
for(int temp2 = 1 ; temp2 < total_number + 1 ; temp2++){
if(temp1 < time[temp2 - 1])
dp[temp1][temp2] = dp[temp1][temp2 - 1];
else
dp[temp1][temp2] = max(dp[temp1 - time[temp2 - 1]][temp2 - 1] + value[temp2 - 1] , dp[temp1][temp2 - 1]);
}
}
cout << dp[total_time][total_number];
return 0;
}
当然如果条件给个数子太大,开二维数组可能会RE,所以我们可以进行优化
由上可知:
dp[ i ] [ j ] = max(dp[ i - weight[ j ] ] [ j - 1 ] + value[ j ] , dp[ i ] [ j - 1])
我们将两个维度互换一下:
dp[ j ] [ i ] = max( dp[ j - 1] [ i - wight[ i ]] + value [ j ] , dp [ j - 1] [ i ])
可以观察到第一个维度只有i - 1 了,i - 1之前的数据都无效了,所以我们可以将它优化为一维数组
即:
dp[ i ] = max(dp[ i - weight [ i ]] + value[j] , dp[i - 1])
有一个重要的点是
必须要从后往前遍历,应为你要使用的值应该是之前的值,如果从前遍历的话,值会更新
#include
using namespace std;
int main()
{
int total_time , total_number;
cin >> total_time >> total_number;
int time[total_number] = {0} , value[total_number] = {0};
for(int temp = 0 ; temp < total_number ; temp++)
cin >>time[temp] >> value[temp];
int dp[total_time + 1] = {0};
for(int temp1 = 0 ; temp1 < total_number ; temp1++){
for(int temp2 = total_time ; temp2 >= 0 ; temp2--){
if(temp2 < time[temp1])
continue;
else
dp[temp2] = max(dp[temp2 - time[temp1]] + value[temp1] , dp[temp2]);
}
}
cout << dp[total_time];
return 0;
}