0-1背包问题
有n件物品,第i件物品(I = 1,2,3…n)的价值是vi, 重量是wi,我们有一个能承重为m的背包,我们选择一些物品放入背包,显然放入背包的总重量不超过m。我们要求选择物品的总价值最大,请问如何选择?这里我们假设所有出现的数都是正整数。
输入
第1行,2个整数,N和W中间用空格隔开。N为物品的数量,W为背包的容量。(1 <= N <= 100,1 <= W <= 10000) 第2 - N + 1行,每行2个整数,Wi和Pi,分别是物品的体积和物品的价值。(1 <= Wi, Pi <= 10000)
输出
输出可以容纳的最大价值。
输入示例
3 6 2 5 3 8 4 9
输出示例
14
(1) 枚举。但对于n件物品,每件都可以选择取或者不取,总的可能性有2n, n = 30就大约已经有10亿种可能了!枚举所有可能选择一种不超过背包承重并且价值最大的物品组合,枚举量太大了。
(2) 贪心。 先选最贵重的物品?找个反例:
n = 3, m = 3
v = (2,2,3)
w = (1,2,3)
按照先选贵重物品的策略,会先选择价值为3的那个,并且背包装满了,但是如果我们选取前两个物品,总价值可以达到4。
可能你已经想到了,考虑“性价比”,先选取“性价比”高的。性价比怎么定义呢?用价值除以重量!先选取单位重量价值最大的物品试试?
再举个例子:
n = 3, m = 7
v = (2,3,4)
w = (3,4,5)
按我们的方法因为2/3 < 3/4 < 4 / 5,我们先选择第三件物品,但是选了它之后别的东西放不下了!总价值是4,但如果我们选择前两件物品可以拿到总价值5。可见这两种贪心法是不行的。
动态规划:
令f(i,j)是决定了前i件物品,总价值恰好是j时的最小重量,那么经过类似的分析,我们可以写出这样的初值和递推式:
f(i,j)=⎧⎩⎨⎪⎪⎪⎪⎪⎪⎪⎪0(i=0∩j=0)∞(i=0∩j>0)f(i−1,j)(i>0∪j<vi)max(f(i−1,j),f(i−1,j−wi)+vi)(i>0∪j>vi)
代码:
二维数组(未优化):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int w[110],v[110];
int dp[110][10010];//dp[i][j] 表示选决定了前i件物品,重量恰好为j的时候能获得的最大价值。
int main()
{
int n,W;
scanf("%d %d",&n , &W);
for(int i = 1; i <= n; i++)
{
scanf("%d %d",&w[i],&v[i]);
}
memset(dp , 0 ,sizeof(dp));
for(int i = 1; i <= n; i++)
{
for(int j = 0; j < w[i] && j < W; j++)
{
dp[i][j] = dp[i-1][j];
//printf("===%d==%d==%d===\n",i,j,dp[i][j]);
} //不选第i件物品时
for(int j = w[i]; j <= W ; j++) //选第i件物品时
{
dp[i][j] = max(dp[i-1][j] , dp[i-1][j-w[i]]+v[i] );
//printf("===%d==%d==%d===\n",i,j,dp[i][j]);
}
}
printf("%d\n",dp[n][W]);
return 0;
}
一维数组(优化):
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int w[110],v[110];
int dp[10010];
int main()
{
int n,W;
scanf("%d %d",&n , &W);
for(int i = 1; i <= n; i++)
{
scanf("%d %d",&w[i],&v[i]);
}
memset(dp , 0 ,sizeof(dp));
for(int i = 1; i <= n; i++)
{
for(int j = W ; j >= w[i]; j--)
dp[j] = max(dp[j] , dp[j - w[i]] + v[i]);
}
printf("%d\n",dp[W]);
return 0;
}