以GOJ 1131 为例
背包1
Time Limit: 2000/1000ms (Java/Others)
Problem Description:
有 n 个重量和价值分别为Wi,Vi的物品,现从这些物品中挑选出总量不超过 W 的物品,求所有方案中价值总和的最大值。
Input:
输入包含多组测试用例,每一例的开头为两位整数 n、W(1<=n<=10000,1<=W<=1000) ,接下来有 n 行,每一行有两位整数 Wi、Vi(1<=Wi<=10000,1<=Vi<=100)。
Output:
输出为一行,即所有方案中价值总和的最大值。
Sample Input:
3 4
1 2
2 5
3 7
Sample Output:
9
01背包也是动态规划的题目,所有的物品都只有两种状态,放入背包(1)或者不放(0)。既然是一道dp题目,那么我们先要找到子问题,由子问题得到最终问题得结果。背包总量为W,有n个物品,要求价值总和的最大值。那么当总量为W时,背包价值的最大值可以由放入的第i件物品背包的价值(W-wi)加上a[i]的和与未放入物品i的背包W比较,取两者的最大值。以此类推,可得状态转移方程为:
dp[i][j] = max(dp[i-1][j],dp[i -1][j - w[i]]+a[i])
(dp[i][j] 表示总量为j的背包,放入前i个物品所取得价值的最大值)
为加深理解可以借助表格(数据以上题为例)
ps:两张表除了物品顺序不一样外,最终的结果都是一样的。生成表的顺序从下到上,从左到右。
表一:
举个例子(为方便叙述,用物品代号和背包总量表示蓝色格子的数据)
在表二中(3,2) = 0,代表只有物品3时,有个承重为2的背包,那么这个背包的最大价值是0,因为物品3的重量是3,背包装不了。
表二(1,4) = 9,代表有物品1,2,3时,有个承重为4的背包,这个背包的最大价值是9, 这时怎么得到的呢? 根据状态转移方程我们需要看两个值,
[2,3] + vlaue[1] = 9,与 [2,4] = 7。
[2,4]表示的是只有物品2,3两件可选时,这个背包能装入的最大价值。
[2,3]表示有一个承重为3的背包(等于当前背包[2,4]承重减去物品1的重量),当只有物品2,3件可选时,这个背包能装入的最大价值。
那么加上物品1的价值后 ,很明显该单元格填上9。
那么接下来只要模拟这个表格的生成就可以了。
#include
#include
#include
using namespace std;
int main()
{
int n;
int Z;
int w[10004];
int a[10004];
static int dp[10004][1004];//数组太大会造成栈溢出,所以不能放在栈中,定义为静态保存在堆中或者改为全局变量
while(scanf("%d%d",&n,&Z)!=EOF)
{
//初始化
memset(w,0,sizeof(w));
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
for(int i = 1 ; i <= n; i++)
scanf("%d%d",&w[i],&a[i]);
for(int i = 1 ; i <= n; i++)
for(int j = 0; j <= Z; j++)
{
if(w[i] > j)
{//装不下的另外处理
if(j == 0)
dp[i][j] = 0;
else
dp[i][j] = dp[i - 1][j];
}
else
dp[i][j] = max(dp[i-1][j],dp[i - 1][j - w[i]] + a[i]);
}
printf("%d\n",dp[n][Z]);
}
return 0;
}
空间复杂度优化:
因为是求所有方案中价值总和的最大值,那么我们只要保存最终表的最顶行的数据即可,可以优化到O(Z)。
先考虑上面基本思路的实现,一个主循环i=1..n,每次算出来二维数组dp[i][0..Z]的所有值。那么,用一个数组f[0..Z],保证第i次循环结束后f[z]中表示的就是我们定义的状态dp[i][j]。此外dp[i][j]是由dp[i-1][j]和dp[i-1][j-w[i]]两个子问题递推而来。在推dp[i][j]时(也即在第i次主循环中推f[j]时)要能够得到dp[i-1][j]和dp[i-1][j-w[i]]的值,要求在每次主循环中我们以j=Z..0的顺序推f[j],这样才能保证推f[j]时f[j-w[i]]保存的是状态dp[i-1][j-w[i]]的值。如果将z的循环顺序从上面的逆序改成顺序的话,那么则成了dp[i][j]由dp[i][j-w[i]]推知,与本题意不符(另外的完全背包问题),故用一维数组解决的方法也很重要。
生成一维数组dp[j]表格(变化方向: 从左到右,从上到下 ):
同样,只要能填上面表格,那么代码也就容易写了。
#include
#include
using namespace std;
int main()
{
int n;
int Z;
int w[10004];
int a[10004];
int dp[1004];
while(scanf("%d%d",&n,&Z)!=EOF)
{
memset(w,0,sizeof(w));
memset(a,0,sizeof(a));
memset(dp,0,sizeof(dp));
for(int i = 1 ; i <= n; i++)
scanf("%d%d",&w[i],&a[i]);
for(int i = 1; i <= n; i++)
for(int j = Z; j >= w[i]; j--)
dp[j] = max(dp[j - w[i]] + a[i], dp[j]);
printf("%d\n",dp[Z]);
}
return 0;
}
上交结果表明优化空间复杂度,在数据很大的时候,运行时间的差距是相当可观的。