dcf
背包是一类典型的动归问题,有大神总结出来的背包九讲,0-1背包是其中的一种,也是最基本的一种。
之所以叫做 0-1背包是因为,这类问题中的每一件物体只有一件,放入背包与不放就构成了它的两种状态,0 代表不放,1 代表放入。
将这类问题总结一下就是:
解这道题的中点就在于,对于上面这个状态转移方程的理解。也就是把i个物品放入重量限制为W背包的中所取得的最大价值,取决于第i个物品的两种状态,即放与不放。
放,由于它会占有一定的重量,所以能取得的价值为v[i](它本身的价值)+Value(i-1, w-w[i])(前i-1件物品放入重量限制为w-w[i]的背包能取得的最大价值);
不放(放不下也属于),所能取得的价值就是Value(i-1, w)(即前i-1件物品放入重量限制为w的背包中)。
Value(i, w)即i件物品放入重量限制为w的背包中的最大价值就等于上面两种情况中的最大值。
这是一个动归理解的思想,我们在将它自底向上倒回去实现。
例如, 存在一个背包重量限制为10, 有5个物品重量价值分别为(5, 1)(2, 4)(3, 3)(4, 2)(5, 1),求最大价值。
此时我们用一个二位数组来记录对应的每一个value(i,w),从第一个物品开始放起,算出每一种情况下的最大价值,并逐步退出最终的解,即在重量限制10下,所能获得的最大价值。
(i,w) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
1 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
2 | 0 | 0 | 0 | 0 | 2 | 2 | 2 | 2 | 2 | 3 | 3 |
3 | 0 | 0 | 0 | 3 | 3 | 3 | 3 | 5 | 5 | 5 | 5 |
4 | 0 | 0 | 4 | 4 | 4 | 7 | 7 | 7 | 7 | 9 | 9 |
5 | 0 | 5 | 5 | 9 | 9 | 9 | 12 | 12 | 12 | 12 | 14 |
类似于数字塔一样,我们可以从第一个物品开始装,并计算出各个情况下最大的价值,这样就可以依次计算二维数组中每一层的结果,最后输出[5,10]即在重量限制为10时放入第5个物品的最大价值。以这一题为例,结果就是v[5, 10] = 14。
Problem Description
Bessie has gone to the mall’s jewelry store and spies a charm bracelet. Of course, she’d like to fill it with the best charms possible from the N (1 ≤ N ≤ 3,402) available charms. Each charm i in the supplied list has a weight Wi (1 ≤ Wi ≤ 400), a ‘desirability’ factor Di (1 ≤ Di ≤ 100), and can be used at most once. Bessie can only support a charm bracelet whose weight is no more than M (1 ≤ M ≤ 12,880).Given that weight limit as a constraint and a list of the charms with their weights and desirability rating, deduce the maximum possible sum of ratings.
Input
- Line 1: Two space-separated integers: N and M
- Lines 2..N+1: Line i+1 describes charm i with two space-separated integers: Wi and Di
Output
- Line 1: A single integer that is the greatest sum of charm desirabilities that can be achieved given the weight constraints
Sample Input
4 6
1 4
2 6
3 12
2 7
Sample Output
23
按照我们上面的思路我们就可以写出这样的代码
#include <iostream>
#include <algorithm>
using namespace std;
int w[3500], d[3500];
int f[13000][13000];
int main()
{
int n, wl;
cin>>n>>wl;
for(int i=1; i<=n; i++) cin>>w[i]>>d[i];
for(int i=1; i<=n; i++)
{
for(int j=1; j<=wl; j++)
{
if(j>=w[i]) f[i][j] = max(f[i-1][j], f[i-1][j-w[i]]+d[i]);
else f[i][j] = f[i-1][j];
}
}
cout<<f[n][wl]<<"\n";
}
但事实上我们这样是过不了的,在其他oj上面可能有数据量比较小的题可以过,但是对于这道题来说,是会这样的Memory Limit Exceeded
。
其实也很好解决,因为我们仔细观察就可以发现,我们从底层往上推的时候,实际上只用到了当前的和上一列,此时我们就可以让数组变成这样 f[2][13000]。
for(int i=1; i<=n; i++)
{
for(int j=1; j<=wl; j++)
{
if(j-w[i]>=0) f[1][j] = max(f[0][j], f[0][j-w[i]]+d[i]);
else f[1][j] = f[0][j];
}
for(int j=0; j<=wl; j++) f[0][j] = f[1][j];
}
相当于滚动着去利用这个数组,这样做后,我们会发现,两列其实也是没必要的,可以利用一个一维数组,因为要用到前面的值,所以我们每一行的推导,变成从后往前,就不会影响到值的变化了。
#include <iostream>
#include <algorithm>
using namespace std;
int w[3500], d[3500];
int f[13000];
int main()
{
int n, wl;
cin>>n>>wl;
for(int i=1; i<=n; i++) cin>>w[i]>>d[i];
for(int i=1; i<=n; i++)
{
for(int j=wl; j>=1; j--)
{
if(j-w[i]>=0) f[j] = max(f[j], f[j-w[i]]+d[i]);
else f[j] = f[j];
}
}
cout<<f[wl]<<"\n";
}