什么是背包问题
背包问题指这样一类问题,题意往往可以抽象成:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。
一、01背包问题
1.问题描述: 有n件物品且每件物品只有一个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
2.解题思路: dp问题首先解决:状态表示,状态集合,状态属性,集合划分。
1.状态表示:
f[i][j]
表示前i
个物品中,背包体积为j
的最优解(最大价值)。
2.状态集合:只从前i
个物品选,背包总体积不大于j
下的最优解。
3.状态属性:最优解为背包总价值最大。
4.集合划分:这部为求状态转移方程的关键,第i
个物品集合可分为两种情况:不选第i
件物品,选第i
件物品。1.不选第
i
件物品:f[i][j] = f[i - 1][j]
。
2,选第i
件物品:f[i][j] = f[i - 1][j - v[i]] + w[i]
最终状态转移方程为:
f[i][j] = max(f[i - 1][j] , f[i - 1][j - v[i]] + w[i])
n // 物品数
m // 背包容量
for (int i=1; i<=n; i++)
{
for (int j=1; j<=m; j++)
{
f[i][j] = f[i-1][j];
// 当背包容量小于物品容量j < v[i]时,f[i-1][j]代表一个集合。
if (j >= v[i]) f[i][j] = max(f[i-1][j],f[i-1][j-v[i]] + w[i]);
}
}
3.优化: 将二维优化成一维f[j]
,表示容量为j
的最优解。
二维 f[i][较大体积] = max(f[i - 1][j] , f[i - 1][较小体积] + w[i])
,可以观察到状态转移方程应该由上一个状态去更新此时状态。
一维 f[较大体积] = max(f[j] , f[较小体积] + w[i])
,没办法自定义访问,那我们就要保证上一个状态不被更新。
如果从小到大遍历j
那么f[较小体积]
就会被先更新,而后更新f[较大体积]
就不是上一个状态的 f[较小体积]
,而是此状态的 f[较小体积]
。
解决方法:从大到小遍历j,这样更新f[较大体积]
的就是上一个状态的 f[较小体积]
。
每次遍历时 f[j] = f[上一状态的 j]
与 f[j - v[i]] + w[i]
比较大小更新 f[j]
。
for (int i=1; i<=n; i++)
{
for (int j=m; j>=v[i]; j--)
{
f[j] = max(f[j],f[j-v[i]] + w[i]);
}
}
二、完全背包问题
1.问题描述: 有n件物品且每件物品有无限个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
2.解题思路: 与01背包类似只不过这里的物品是无限个,那么我们就枚举无限个,前提不超过背包容量。
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
for (int k = 0; k * v[i] <= j; k++) // 枚举物品个数
{
f[i][j] = max(f[i][j], f[i-1][j - v[i] * k] + k * w[i]);
// k = 0时 f[i][j] = f[i-1][j]
}
}
}
3.优化: 我们列举一下更新次序的内部关系
f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2*v] + w , f[i-1,j-2*v]+2*w , .....)
由上两式,可得出如下递推关系:
f[i][j]=max(f[i - 1][j], f[i][j - v] + w);
有了上面的关系,那么其实k循环可以不要了
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
f[i][j] = f[i - 1][j];
if (j >= v[i]) f[i][j] = max(f[i][j], f[i][j - v[i]] + w[i]);
}
}
二维降到一维
for (int i = 1; i <= n; i++)
{
for (int j = v[i]; j <= m; j++)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
// f[i][j] = max(f[i-1][j],f[j - v[i]] + w[i]);
// f[j] 为上轮的f[j]
// f[j - v[i]]为这轮确定
}
}
三、多重背包问题
1.问题描述: 有n件物品且每件物品有S个和一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
2.解题思路: 每件物品有S个,用 s[i] 来存每件商品的数量,与完全背包类似枚举每件物品的数量。
//二维
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= m; j++)
{
for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
{
f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
}
}
}
//一维
for (int i = 1; i <= n; i++)
{
for (int j = m; j > 0; j--)
{
for (int k = 0; k * v[i] <= j && k <= s[i]; k++)
{
f[j] = max(f[j], f[j - k * v[i]] + k * w[i]);
}
}
}
3.优化: 这里优化采用二进制,例如 1,2,4,8 ··· 总数为 n ,利用 1,2,4,8 ··· 可以确定 1 ~ n 之间的所有数。
证明:
例如: 1,2,4 总数为 7。
1(001),2(010),3(001 + 010 = 011),4(100),5(100 + 001 = 101),6(110),7(111)
总结:有 001,010,100 就可确定 001 ~ 111 之间的所用数。
但如果总数时 10,方案为 1,2,4,8 总数是 12 比 10 要大,这时我们就选 1,2,4,3 总数为 10
其中 1,2,4 可以确定 1 ~ 7 之间的所有数,那么 1,2,4,3 可以确定 4 (1 + 3) ~ 10 (7 + 3) 与 1 ~ 7 的并集 1~10。
由上述证明,我们就可以把第 i 件物品 S 个 打包成 1,2,4,8 ··· 和为 S,若不能恰好为 S 那么利用上述总数时 10 的样例打包,这样物品就可以选 1 ~ S 个,被打包的可以看成一个新的物品,之后就是01背包问题。
for (int i = 0; i < n; i++)
{
int a, b, c; cin >> a >> b >> c;
int k = 1;
while (k <= c)
{
v[++cnt] = a * k; // 打包后的体积
w[cnt] = b * k; // 打包后的价值
c -= k; // 打包完剩下的件数
k <<= 1; // k = 1,2,4,8 ···
}
if (c != 0) v[++cnt] = a * c, w[cnt] = b * c;
// 不能恰好等于,把剩下的打包
}
n = cnt;
for (int i = 1; i <= n; i++)
{
for (int j = m; j > v[i]; j--)
{
f[j] = max(f[j], f[j - v[i]] + w[i]);
}
}
四、分组背包问题
1.问题描述: 有n组物品且每组物品有若干个,同一组内的物品最多只能选一个,一个容量为V的背包。第i件物品的体积是v[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的体积总和不超过背包容量,且价值总和最大。
直接上代码
// 二维
for (int i = 1; i <= n; i++)
{
cin >> s[i]; // 记录第i组的物品的个数
for (int j = 1; j <= s[i]; j++)
{
cin >> v[i][j] >> w[i][j]; // 第i组第j个物品
}
}
for (int i = 1; i <= n; i++) // 枚举组数
{
for (int j = 1; j <= m; j++) // 背包容量
{
for (int k = 0; k <= s[i]; k++) // 枚举i组的物品
{
if (j >= v[i][k]) f[i][j] = max(f[i][j], f[i - 1][j - v[i][k]] + w[i][k]);
// 这里的if不可以写在for里面
// 如果写在for里面那么第k个使得 j < v[i][k] 的物品就会直接退出for
// 后面的 k + n 可能 j >= v[i][k]
}
}
}
//一维
for (int i = 1; i <= n; i++)
{
cin >> s[i];
for (int j = 1; j <= s[i]; j++)
{
cin >> v[i][j] >> w[i][j];
}
}
for (int i = 1; i <= n; i++)
{
for (int j = m; j > 0; j--)
{
for (int k = 1; k <= s[i]; k++)
{
if (j >= v[i][k]) f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);
}
}
}