背包问题
01背包问题:
01背包问题描述:有编号分别为a,b,c,d,e的五件物品,它们的重量分别是2,2,6,5,4,它们的价值分别是6,3,5,4,6,每件物品数量只有一个,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
(有n个重量和价值分别为wi和Vi的物品。从这些物品中挑选出重量不超过W的物品,求所有挑选方案中价值总和的最大值。)
动态规划的基本思路:将该问题转换成子问题,考虑五件物品在给定承重 W 的背包下最大价值为原问题,如下表所示,即为考虑abcde,W = 10时的最大价值,假设为f[5][10],原问题的解可以分解为两种情况,第一种情况是不考虑放入a只考虑放入bcde承重为W时的最大价值f[4][W],第二种情况是考虑放入a时的最大价值,即value[a]+f[4][10-weight[a]]。 原问题的解f[5][10]取上述两种情况中的最大值,即f[5][10] = max{f[4][10], value[a]+f[4][10-weight[a]]}。 由此可以看出里面涉及到需要计算f[4][10]和f[4][10-weight[a]]即f[4][4]等子问题。 以此类推,自顶向下的分析可以看出原问题需要子问题的解,我们需要先计算出子问题的解,自底向上求解。求解方式如下表所示,顺序是自底向上、从左往右,或者从左往右、自底向上都可以。注意此问题中的abcde可以包含相同的物件,它们之间的顺序也可以是任意的,不影响最终的结果。
name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
e | 4 | 6 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 6 | 6 |
d | 5 | 4 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 10 |
c | 6 | 5 | 0 | 0 | 0 | 0 | 6 | 6 | 6 | 6 | 6 | 10 | 11 |
b | 2 | 3 | 0 | 0 | 3 | 3 | 6 | 6 | 9 | 9 | 9 | 10 | 11 |
a | 2 | 6 | 0 | 0 | 6 | 6 | 9 | 9 | 12 | 12 | 15 | 15 | 15 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
01背包代码之1:
//用dp针对不同的重量限制计算最大的价值
//dp[i+1][j]:表示从0到i这i个物品中选出重量不超过j的物品时的总价值的最大值
//dp[0][j]=0;
const int maxn = 100;
int dp[maxn][maxn]; //dp数组
int w[maxn]; //物品重量
int v[maxn]; //物品价值
int W; //背包能承受的最大重量
int n; //物品个数
void solve()
{
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= m; j++)
{
if (j < w[i])
dp[i][j] = dp[i-1][j];
else
dp[i][j] = max(dp[i-1][j], dp[i-1][j - w[i]] + v[i]);
}
}
//printf("%d\n", dp[n][W]);
}
01背包代码之2:
//当重量的数组规模太大不够用时,可以改变dp的对象,用dp针对不同价值计算最小的重量
//dp[i+1][j]:前i个物品挑选出价值总和为j时的总重量的最小值(不存在时就是一个充分大的数值INF)
//dp[0][0]=0;
//dp[0][j]=INF;
const int maxn = 100;
int dp[maxn][maxn*maxn+1]; //dp数组
int w[maxn]; //物品重量
int v[maxn]; //物品价值
int W; //背包能承受的最大重量
int n; //物品个数
void solve()
{
fill(dp[0], dp[0] + maxn*maxn + 1, INF);
dp[0][0] = 0;
for (int i = 0; i < n; i++)
{
for (int j = 0; j <= maxn*maxn; j++)
{
if (j < v[i])
dp[i + 1][j] = dp[i][j];
else
dp[i + 1][j] = min(dp[i][j], dp[i][j - v[j]] + w[j]);
}
}
int res = 0;
for (int i = 0; i <= maxn*maxn;i++)
if (dp[n][i] <= W)
res = i;
printf("%d\n",res);
}
完全背包问题:
完全背包问题描述:有编号分别为a,b,c,d的四件物品,它们的重量分别是2,3,4,7,它们的价值分别是1,3,5,9,每件物品数量无限个,现在给你个承重为10的背包,如何让背包里装入的物品具有最大的价值总和?
(有n种重量和价值分别为wi和vi的物品。从这些物品中挑选重量不超过W的物品,求出挑出物品价值总和的最大值。每件物品可以挑选任意多件。)
完全背包问题与01背包问题的区别在于每一件物品的数量都有无限个,而01背包每件物品数量只有一个。
问题解法其实和01背包问题一样,只是初始化的值和递推公式需要稍微变化一下。初始化时,当只考虑一件物品a时,f[1][j] = j/weight[a]。 递推公式计算时,f[i][y] = max{f[i-1][y], (f[i][y-weight[i]]+value[i])},注意这里当考虑放入一个物品 i 时应当考虑还可能继续放入 i,因此这里是f[i][y-weight[i]]+value[i], 而不是f[i-1][y-weight[i]]+value[i]。
name | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
a | 2 | 1 | 0 | 0 | 1 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 |
b | 3 | 3 | 0 | 0 | 1 | 3 | 3 | 4 | 6 | 6 | 7 | 9 | 9 |
c | 4 | 5 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 8 | 10 | 10 | 11 |
d | 7 | 9 | 0 | 0 | 1 | 3 | 5 | 5 | 6 | 9 | 10 | 10 | 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
完全背包代码:
//dp[i+1][j]:表示从前i个物品中选出重量不超过j的物品时的总价值的最大值
//dp[0][j]=0;
const int maxn = 100;
int dp[maxn][maxn]; //dp数组
int w[maxn]; //物品重量
int v[maxn]; //物品价值
int W; //背包能承受的最大重量
int n; //物品个数
void solve()
{
fill(dp[0], dp[0] + W + 1, INF); //注意初始化的问题
dp[0][0] = 0;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= W; j++)
if (j < w[i])
dp[i][j] = dp[i - 1][j];
else
dp[i][j] = max(dp[i - 1][j], dp[i][j - w[i]] + v[i]);
}
//printf("%d\n", dp[n][W]);
}
多重背包问题:
多重背包问题描述:有编号分别为a,b,c的三件物品,它们的重量分别是1,2,2,它们的价值分别是6,10,20,他们的数目分别是10,5,2,现在给你个承重为 8 的背包,如何让背包里装入的物品具有最大的价值总和?
多重背包和01背包、完全背包的区别:多重背包中每个物品的个数都是给定的,可能不是一个,绝对不是无限个。
将原数量为k的物品拆分成若干组,每一组可看成一件新的物品,其价值和重量为改组中所有物品的价值重量的总和,每组物品包含的原物品个数分别为:1、2、4...k-2^c+1,其中c为使k-2^c+1大于0的最大整数。这样就将物品数量大大降低,同时通过对这些若干个原物品组合得到的新物品的不同组合,可以得到0到k之间的任意件物品的价值重量和,所以对所有这些新物品做0-1背包,即可得到多重背包的解。转化之后的时间复杂度为O(V*∑ni=1log2(ki))。
int v[maxn];
int w[maxn];
int num[maxn];
int dp[maxn];
void zp(int wei, int val, int tot) {//01(质量,价值,容量)
for (int i = tot; i >= wei; i--)
dp[i] = max(dp[i], dp[i - wei] + val);
}
void cp(int wei, int val, int tot) {//完全(质量,价值,容量)
for (int i = wei; i <= tot; i++)
dp[i] = max(dp[i], dp[i - wei] + val);
}
void mp(int wei, int val, int num, int tot) {//多重(质量,价值,数量,容量)
if (wei * num >= tot) cp(wei, val, tot);
else {
int k = 1;
while (k <= num) {
zp(k * wei, k * val, tot);
num -= k;
k *= 2;
}
zp(num * wei, num * val, tot);
}
}