#include"cstdio" #include"cstring" #include"algorithm" using namespace std; int N,W; int w[105],p[105]; int dp[10005]; void DP() { for(int i=1; i<=N; i++) for(int c=W; c>=w[i]; c--) dp[c] = max(dp[c],dp[c-w[i]]+p[i]); } int main() { int t; scanf("%d",&t); while(t--){ memset(dp,0,sizeof(dp)); scanf("%d%d",&N,&W); for(int i=1;i<=N;i++) { scanf("%d%d",&w[i],&p[i]); } DP(); printf("%d ",dp[W]); } return 0; }
01背包的理解,二维数组化一维数组的理解(附hdu2602 Bone Collector)
01背包问题:
有n个物品和一个容量为v的背包,用val[i]表示第i个物品的价值,用vol[i]表示第i个物品的体积,那么,如何使背包里装的物品的总价值最大呢?
贪心是不行的,举个反例:
n=3, v=100
val[i] | vol[i] |
---|---|
80 | 60 |
50 | 50 |
50 | 50 |
按照val[i]/vol[i]比值从大到小贪心,那么会得到错误答案80,但是正确答案是100
动态规划的思想:
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=0; j<=v; j++){
if(j>=vol[i])
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i]);
else
dp[i+1][j] = dp[i][j];
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
如何理解这段代码呢?我们设dp[i][j]为前i件物品放在容量为j的背包里所能得到的最大价值,那么思考一下,每个物品都只有两种可能,放或不放,这就是为什么叫做01背包的原因。那么,我们将第i件物品放在容量为j的背包中,使dp[i][j]最大,那么对于第i件物品,也只有两种操作。于是,我们可以很容易想到,我们不放第i件物品时,是不是需要先知道dp[i-1][j]的值呢?这就形成了dp,形成了一种递推关系。另一种可能,假设我们放第i件物品,那么,首先需要考虑当前容量j是否放得下物品i,假设放得下,将j减去vol[i],即在剩余的体积放前i件所得到的最大的价值为dp[i][j-vol[i]],所以总价值为dp[i][j-vol[i]] +val[i]。
那么,状态转移方程为:
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i])
或者:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-vol[i]]+val[i])
这两种写法要注意数组是从0开始还是从1开始
- 1
- 2
- 3
- 4
- 5
- 6
明显的,时间复杂度是O(n*v)
但是我们还能将空间复杂度降低,从二维降为一维。
看下面这段代码:
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++){
for(int j=v; j>=vol[i]; j--){
dp[j] = max(dp[j], dp[j-vol[i]]+val[i])
}
}
- 1
- 2
- 3
- 4
- 5
- 6
如何理解二维降一维呢?对于外层循环中的每一个i值,其实都是不需要记录的,在第i次循环时,所有的dp[0…v]都还未更新时,dp[j]还记录着前i-1个物品在容量为j时的最大价值,这样就相当于还记录着dp[i-1][j]和dp[i-1][j-vol[i]]+val[i]。
为什么要从v开始递减遍历?我举个例子,假设一个物品GG价值1000,体积为2,那么假设我们按【0…..v】这个顺序遍历,那么在j=2时,dp[2] = max(dp[2], dp[0]+1000),那么dp[2] = 1000,当j=4时,dp[4]=max(dp[4], dp[2]+1000), dp[4] = 2000,这时我们再思考一下,GG将被放进背包两次!!,如果我们逆序遍历,就可以避免这种结果。
此外,这里可以进行一个常数优化,将j>=vol[i]写进for循环中。
大家可以看一下hdu2602这一题,是一题单纯的01背包。
题目:
Bone Collector
Problem Description
Many years ago , in Teddy’s hometown there was a man who was called
“Bone Collector”. This man like to collect varies of bones , such as
dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip
of collecting there are a lot of bones , obviously , different bone has
different value and different volume, now given the each bone’s value
along his trip , can you calculate out the maximum of the total value
the bone collector can get ?
Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two
integer N , V, (N <= 1000 , V <= 1000 )representing the number of
bones and the volume of his bag. And the second line contain N integers
representing the value of each bone. The third line contain N integers
representing the volume of each bone.
Output
One integer per line representing the maximum of the total value (this number will be less than 231).
Sample Input
1
5 10
1 2 3 4 5
5 4 3 2 1
Sample Output
14
用二维数组解:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1000+10;
int val[maxn];
int vol[maxn];
int dp[maxn][maxn];
int main(){
int t, n, v;
cin>>t;
while(t--){
cin>>n>>v;
memset(vol, 0, sizeof(vol));
memset(val, 0, sizeof(val));
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++)
cin>>val[i];
for(int i=0; i<n; i++)
cin>>vol[i];
for(int i=0; i<n; i++){
for(int j=0; j<=v; j++){
if(j>=vol[i])
dp[i+1][j] = max(dp[i][j], dp[i][j-vol[i]]+val[i]);
else
dp[i+1][j] = dp[i][j];
}
}
cout<<dp[n][v]<<endl;
}
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
用一维数组解:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn = 1000+10;
int val[maxn];
int vol[maxn];
int dp[maxn];
int main(){
int t, n, v;
cin>>t;
while(t--){
cin>>n>>v;
memset(vol, 0, sizeof(vol));
memset(val, 0, sizeof(val));
memset(dp, 0, sizeof(dp));
for(int i=0; i<n; i++)
cin>>val[i];
for(int i=0; i<n; i++)
cin>>vol[i];
for(int i=0; i<n; i++){
for(int j=v; j>=vol[i]; j--){
dp[j] = max(dp[j], dp[j-vol[i]]+val[i]);
}
}
cout<<dp[v]<<endl;
}
return 0;
}