特点:每个物品仅有一个。
0-1背包问题中的0.1一般表示物品取或者不取的2种状态。这是其他背包问题的基础。
如果采取贪心算法,求出的结果是错误的,如计算每一个物品的Vi/Wi,然后把物品进行从大到小进行排序。
0-1背包问题的状态转换方程 :f[i][j] = max(f[i-1][j-Wi] + V[i] , f[i-1][j])
f[i][j]表示在前i件中物品中选择若干件(不是所有物品,只是最优的选择一部分)放在承重为j的背包中可以取得最大价值。
状态转移方程表示的是对第i件物品取或者不取的决策。Wi表示第i件物品的质量,Vi表示第i件物品的价值,第一项表示第i件物品被取,第二项表示第i件物品不取,则会转移到i-1个物品中的状态。
理解DP的关键在于能否正确的打表,并找出规律(熟悉以后就可以放弃表格的方式)。
代码如下:
#include <cstdio>
#include <iostream>
#include <cstring>
#define maxn 1000
using namespace std;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int weight[maxn];
int value[maxn];
int f[maxn][maxn];
memset(weight,0,maxn);
memset(value,0,maxn);
int n; // 物品个数
int m; // 背包重量
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&weight[i],&value[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(weight[i]<=j)
f[i][j] = max(f[i-1][j-weight[i]]+value[i] , f[i-1][j]);
else
f[i][j] = f[i-1][j];
}
printf("%d\n",f[n][m]);
return 0;
}
上述算法可以进一步优化内存,在计算f[i][j]的时候只使用了f[i-1][0…j],而没有使用其他的子问题,因此在存储子问题的解时候,只需要存储f[i-1]的解即可(我们需要的是最终的f[n][m],因此可以一步步的舍弃前面的子问题的解,只需要保存上一步的结果即可)。因此可以使用2个一维数组进行实现,一个人数组存储上一步子问题的解,一个数组存储正在解决的子问题。
内存优化:
状态转移方程f[i][j] = max(f[i-1][j-Wi] + V[i], f[i-1][j])中因为当前状态仅仅依赖于前一状态的剩余体积和Vi的值,所以状态转移方程可以简化为一维数组f[j]=max(f[j],f[j-w[i]]+v[i]) 。方程的两侧都有f[j],等号左边的是当前状态,右边的是前一状态,为了保证状态转移的正确性(也就是说是转移到前一状态还是对第i个物品获取的状态),应该首先更新等号左边的f[j]。因此内层循环应该从大到小进行循环。
#include <cstdio>
#include <iostream>
#include <algorithm>
#define maxn 1000
using namespace std;
int max(int x,int y)
{
return x>y?x:y;
}
int main()
{
int n;
int m;
int weight[maxn];
int value[maxn];
int f[maxn]={0};
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d %d",&weight[i],&value[i]);
for(int i=1;i<=n;i++)
for(int j=m;j>=weight[i];j++)
f[j]=max(f[j],f[j-weight[i]]+value[i]);
printf("%d\n",f[m]);
return 0;
}