基本模型
有一个容量为V的背包和若干种物品,在一定限制条件下(物品占用容量),最多能放进多少价值的物品。(本质是组合问题)
背包的每个容量就是DP中的“状态”。
01背包
基本模型
有N件物品和一个容量为V的背包。第i件物品的费用(占用体积)是c[i],价值是w[i],求使价值总和最大的方式。(每件物品只有一个)
解决方法
无序变有序:
依次考虑前1、前2...、前i个物品。
定义状态:
f[i][j]表示前i件物品放入容量为j的背包的最大价值。(要枚举每个状态的值)
状态转移方程:
f[i][j] = max(f[i-1][j], f[i-1][j-w[i]] + w[i])
经典例题
骨头收集者
解:横坐标为容量状态,纵坐标为不同的物品(骨头)的重量和价值。枚举每一个状态。所要求的只有最右下角的那个数据。
优化:只使用一维数组记忆数据,但是每次循环要从后往前进行(因为是01背包,每件物品只能放一次,同时下一次操作不能影响上一次的结果),而且只需要进行到价值为w[i]的地方即可(因为小于w[i]的状态当前物品肯定放不下,只需要保留之前的运算结果就行了)。
#include <cstdio>
#include <cstring>
int val[1002] = {0}; // value
int vol[1002] = {0}; // volume
int dp[1002] = {0};
int max(int x, int y)
{
return x >= y ? x : y;
}
int main()
{
int testNum = 0, N = 0, V = 0;
scanf("%d", &testNum);
while(testNum--)
{
memset(dp, 0, sizeof(dp));
scanf("%d%d", &N, &V);
for(int i = 1; i <= N; i++)
scanf("%d", &val[i]);
for(int i = 1; i <= N; i++)
scanf("%d", &vol[i]);
for(int i = 1; i <= N; i++)
for(int j = V; j >= vol[i]; j--)
dp[j] = max(dp[j], dp[j-vol[i]]+val[i]);
printf("%d\n", dp[V]);
}
return 0;
}
完全背包
基本模型
一种物品可以取无数个。
解决方法
*(1)暴力转化为01背包,把一件物品当成多件来处理。
(2)把01背包中的记忆数组调整为从前往后滚动(因为一件物品可以取无数个,前面假如放过该物品,当容量增加时还可以继续放)。
拓展--背包必须装满
解:将一维数组除第一项(初始化为0)以外的每一项都初始化为-1,用-1来表明没有装满(不合格的情况)。在之后的判断中,只要该容量的子情况中包含没有装满的情况,那这种容量就不合格(不可能装满),设置为-1。最后还是看最右下角的值是正常值还是-1。
多重背包
基本模型
一种物品的数量既不是1个也不是无数个。
解决方法
二进制优化:
将物品进行拆分,转换为01背包。
eg. 对于13个相同的物品可以拆分成4组(1、2、4、6),这四组可以组成任意一个1~13之间的数字。
因此可以将一种有C个的物品拆分成:1、2、4、8...、2^(k-1)、C-2^k+1。
拆分参考代码:
int t=1, cnt=1;
while(x >= t)
{
v[cnt] = a * t;
c[cnt++] = b * t;
x -= t;
t <<= 1;
}
if(x)
{
v[cnt] = a * x;
c[cnt++] = b * x;
}
二维费用背包
基本模型
每件物品都具有两种不同的费用。
解决方法
增加一个维度即可。