问题描述:有N个物品,每种物品只要一件,和容量为W一个背包,每个物品的价值为v[i],花费为w[i](此处的花费就是大小),问,将哪些物品装入背包中使得背包中的物品价值最大。
因为每个物品只有一件,则对该物品而言只要放入背包和不放人背包两种可能。所以叫0/1背包。
对于这类问题,现在来整理一下解题思路。
w[i] 表示 第 i 个物品的 重量
v[i] 表示 第 i 个物品的 价值
W 为 包的 容量
f[i][j] 表示 对前 i 个物品 和容量为 j 的背包问题的 最优解
则 对于 第 i 个物品 是否放入 背包中 有两种情况:放 或则 不放
放 : f[i][j] = f[i-1][j-w[i]] + v[i]
f[i-1][j-w[i]] 表示 对 前 i-1 个物品 和容量为 j-w[i] 背包的子问题的最优解
v[i] 表示 第 i 个物品的价值
不放: f[i][j] = f[i-1][j] 不放则对是前 i-1 个物品 和容量为 j 背包的子问题的最优解
所以 状态转移方程: f[i][j]=max{f[i-1][j],f[i-1][j-w[i]] + v[i]}
主要伪代码:
for i from i to n
for j from w[i] to W
f[i][j] = max{f[i-1][j],f[i-1][j-w[i]] + v[i]}
可以对上面的算法进行空间优化!
我们可以用一维数组 dp[j] 来表示 对前 i 个物品 和 容量为 j 的背包的 最优解
循环 i 从 1 到 n,最后 输出 dp[j] 就是所求的最优解
这里的 dp[j] 就相当于 dp[i][j]
通过上面的 态转移方程: f[i][j]=max{f[i-1][j],f[i-1][j-w[i]] + v[i]}
我们知道 要求f[i][j] 需要 知道 f[i-1][j] 和 f[i-1][j-w[i]]
那么 求 dp[j] 就要知道 上一次循环的dp[j] 和 dp[j-w[i]] 因为 有 循环 i 从 1 到 n
那么 如果才能 让我在 更新 dp[j] 的时候 知道 上次循环的 dp[j] 和 dp[j-w[i]] 呢?
在更新 dp[j] 之前,此时的dp[j] 就是 上次循环的 dp[j]
那么 dp[j-w[i]] 呢?
我们知道 在 内循环中 更新 dp[j] 有两种 策略 : 顺序 和 逆序
如果是 顺序 ,在 求 dp[j] 时,由于 j-w[i] 小于 j 所以,dp[j-w[i]] 早已被更新了,
此时的dp[j-w[i]] 是 此次循环的 dp[j-w[i]] 而不是 上一次循环的 dp[j-w[i]],所以这种策略不行。
而 逆序的话 则 正好可行,
在更新 dp[j] 时,dp[j-w[i]] 还没被更新,此时的dp[j-w[i]] 就是上次循环的 dp[j-w[i]]。
主要伪代码如下:
for i from i to n
for j from W to w[i]
f[j] = max{f[j],f[j-w[i]] + v[i]}
练习题
参考代码:
#include <stdio.h>
#include <string.h>
int dp[1010];
int V[1010];
int W[1010];
int main()
{
int cas;
scanf("%d",&cas);
while(cas--)
{
memset(dp,0,sizeof(dp));//别忘初始化
memset(V,0,sizeof(V));
memset(W,0,sizeof(W));
int n,w;
scanf("%d %d",&n,&w);
int i;
for(i=1; i<=n; i++)
scanf("%d",&V[i]);
for(i=1; i<=n; i++)
scanf("%d",&W[i]);
int j;
for(i=1; i<=n; i++)
{
for(j=w; j>=W[i]; j--)
{
dp[j]=dp[j]>(dp[j-W[i]]+V[i])?dp[j]:(dp[j-W[i]]+V[i]);
}
}
printf("%d\n",dp[w]);
}
return 0;
}