背包的基本模型:
给你一个容量为V的背包和若干种物品,在一定的限制条件下(每种物品都占用一定容量),问最多能放多少价值的物品?
一、01背包
01背包
(最基础的背包问题)- 有N件物品和一个容量为V的背包。第i件物品的
费用
是c[ i ],价值是w[ i ]。求解将哪些物品装入背包可使价值总和最大? 问题特点:
每件物品仅有一件
,可以选择放或不放;思考:
在每个物品都有可能被选中的前提下,如何构造“子问题
”?无序变有序的方法
:依次考虑前1、前2、前3…前i个物品;状态定义
:f [ i ][ v ]表示前i件物品
放入一个容量为v的背包可以获得的最大价值。
问题分解:当前最优解,要么包含第i种物品,要么不包含第i种物品。和物品顺序没有关系,不需要排序。
二维数组实现,时间复杂度和空间复杂度都是O(N*V):
for(int i = 1; i <= n; i++)
{
for(int j = 0; j <= v; j++)
{
if(j>=c[i])
{
dp[i][j] = max(dp[i-1][j], dp[i-1][j-c[i]] + w[i]);
}
else
dp[i][j] = dp[i-1][j];
}
}
cout << dp[n][v] << endl;
空间优化O(n):
一维数组,从后往前排,虽然还在同一行,但是没更新,前面的就是上一次的结果。如果顺序遍历,一种物品会被“ 取 ”好多次
for(int i = 1; i <= n; i++)
{
for(int j = v; j >= c[i]; j--)//没必要循环到0,体积小于c[i]当前物品一定放不下
{
dp[i] = max( dp[j], dp[j-c[i]] + w[i]);
}
}
cout << dp[v] << endl;
完全背包
- 完全背包特点:
一种物品可以取无数个
- 可否转化成01背包问题?
- 朴素的转化方式是?
- 回忆01背包为什么要对容量按照逆序循环?
- 和01背包类似,不过就是正着写!
for(int i = 0; i < v; i++)
{
for(int j = 0; j < v; j++)
{
dp[v] = max(f[v], f[v-c[i]] + w[i]);
}
}
深度思考:这类能不能达到的问题应该怎么实现? 用-1初始化,如果剩余空间为-1,则表示无解,memset(dp,-1,sizeof(dp));
多重背包
- 多重背包特点:一种物品有C个(既不是固定的1个,也不是无数个)
dp[i][v] = max(dp[i][v], dp[i-1][v-k*c[i]] + k*w[i]);
for(int i = 1; i <= n; i++)
{
for(int j = v; j >= 0; j--)
{
for(int k = 0; k <= n[i]; k++)
{
if(j >= k*c[i])
{
dp[j] = max(dp[j-k*c[i]] + k*w[i], dp[j]);
}
}
}
}
二维费用背包
- 对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(比如:背包容量、最大承重),求怎样选择物品可以得到最大的价值。
- 设第i件物品所需的两种代价分别为a[ i ]和b[ i ],两种代价可付出的最大值(比如体积或重量)分别为V和U,物品的价值为w[ i ]。
- 对应算法:费用加了一维,只需状态也加一维即可!
- 设f [ i ][ v ][ u ]表示前i件物品付出两种代价分别为v和u时可获得的最大价值,状态转移方程则为:
- f [ i ][ v ][ u ] = max( f [ i-1 ][ v ][ u ], f [ i-1 ][ v-a[i] ][ u-b[i] ] + w[ i ]);