01背包问题
题目描述: ( 转自背包九讲1 )
有N件物品和一个容量为V的背包。第i件物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
基本思路
这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。
用子问题定义状态:即f[i][v]表示前i件物品恰放入一个容量为v的背包可以获得的最大价值。则其状态转移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][v];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为v-c[i]的背包中”,此时能获得的最大价值就是f[i-1][v-c[i]]再加上通过放入第i件物品获得的价值w[i]。
我的理解:
状态转移方程很好理解,我们想你的包是固定大小的,而一堆东西在外面,我们可以选择放当前拿到手的物品,那包的体积肯定要减小,如果我们不放这个物品,那背包里的物品价值和容量当然保持不变了.
我喜欢设状态数组为DP[i][v];
那么下标分别是什么意思呢? 我们可以将 i 当成物品的编号,把v当成当前背包的体积
PKU ACM 有道原题链接:http://acm.pku.edu.cn/JudgeOnline/problem?id=3624
题目大意:
给定一个背包的物品数量和背包体积,然后分别输入每个物品的体积和价值
最后输出背包装满后最大价值量
样例输入:
4 6
1 4
2 6
3 12
2 7
样例输出:
23
我们输出DP数组看一下:
1 2 3 4 5 6
1 4 4 4 4 4 4
2 4 6 10 10 10 10
3 4 6 12 16 18 22
4 4 7 12 16 19 23
最上一排和最左一排是行下标和列下标,我们看到 DP[1][1] 代表什么呢? 代表有第1个物品,并且当前背包体积为1的时候最大的量,当然是4了,而DP[2][2]呢? 代表当前放入物品2,因为当前体积为2,而物品2体积就是2了,所以1就不能放了,好,DP[2][2] = 6,同理,DP[4][6] 就代表体积装满的时候,背包里面的总价值,为23.
这里时间复杂度不能优化了,而我们可以优化空间复杂度,我们观察DP数组的最后一行:
先考虑上面讲的基本思路如何实现,肯定是有一个主循环i=1..N,每次算出来二维数组f[i][0..V]的所有值。那么,如果只用一个数组f [0..V],能不能保证第i次循环结束后f[v]中表示的就是我们定义的状态f[i][v]呢?f[i][v]是由f[i-1][v]和f[i-1] [v-c[i]]两个子问题递推而来,能否保证在推f[i][v]时(也即在第i次主循环中推f[v]时)能够得到f[i-1][v]和f[i-1][v -c[i]]的值呢?事实上,这要求在每次主循环中我们以v=V..0的顺序推f[v],这样才能保证推f[v]时f[v-c[i]]保存的是状态f[i -1][v-c[i]]的值。伪代码如下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i- 1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。
核心程序:
for ( int i = 1; i <= N; i++ )
{
for ( int v = V; v>=1; --v )
{
if ( v >= cost[i] )
{
if ( DP[v-cost[i]] + value[i] >= DP[v] ) {
DP[v] = DP[v-cost[i]] + value[i];
}
}
//DP[v-cost[i]]+value[i]就是说目前包里要装第i个物品
//看看装第i个物品和不装它哪个大,第一次开始时DP[6],就是说可以装
//单位重量的物品,然后依次类推,5,4,3,2,1,看看有或没有第i个物品的总价值
C++ 代码:
#include <iostream>
#include <vector>
using namespace std;
int main ()
{
vector<int> value;
vector<int> volume;
vector<int> DP;
int N,V;
while ( cin >> N >> V )
{
value.resize(N+1);
volume.resize(V+1);
DP.clear();
DP.resize(V+1);
for ( int i = 1; i <= N; i++ )
{
cin >> volume[i] >> value[i];
}
for ( int i = 1; i <= N; i++ )
{
for ( int v = V; v >= 1; v-- )
{
if ( v >= volume[i] )
{
if ( DP[v-volume[i]] + value[i] >= DP[v] )
{
DP[v] = DP[v-volume[i]] + value[i];
}
}
}
}
cout << DP[V] << endl;
}
return 0;
}