看到文章末尾有模板福利哦~
介绍:
01背包
有 N 件物品和一个容量是 V的背包。每件物品只能使用一次。
第 i 件物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。
输出最大价值。
二维dp解法:
动态规划数组定义:
f[i][j]
表示只考虑前i
件物品,当背包容量为j
时的最大价值。
一、如果装不下当前物品
那么前n个物品的最佳组合和前n-1个物品的最佳组合是一样的。
二、如果装得下当前物品
假设1:
装当前物品,在给当前物品预留了相应空间的情况下,前n-1个物品的最佳组合加上当前的价值就是总价值
假设2:
不装当前物品,那么前n个物品的最佳组合和前n-1个物品的最佳组合是一样的。
选取假设1和假设2中的较大的价值,为当前的最佳组合的价值。
如果你看懂了以上的所有的流程,那现在下面这个式子就可以理解了。
01背包的状态转换方程 f[i,j] = Max{ f[i-1,j-Wi]+Pi( j >= Wi ), f[i-1,j] }
for (int i = 1; i <= n; i++)
for (int j = 0; j <= m; ++j)
{
f[i][j] = f[i - 1][j];
if (j >= v[i])f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
}
一维dp解法:
动态规划数组定义:
f[j]表示背包容量为j时,能装入的最大价值。
状态转移方程:
对于每个物品i,考虑是否放入背包中。若不放入,则总价值不变;
若放入,则总价值为f[j-v[i]] + w[i](在当前容量下,放入物品i后的总价值)。
因此,状态转移方程为f[j] = max(f[j], f[j-v[i]] + w[i])。
特别注意,我们要从后往前遍历,否则如果从前往后,我们使用的值就不是上一个物品的值。
文末有代码模板。
完全背包
有 N 种物品和一个容量是 V 的背包,每种物品都有无限件可用。
第 i 种物品的体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。
二维dp解法:
动态规划数组定义:
使用二维数组
f[i][j]
,其中f[i][j]
表示考虑前i
件物品,当背包容量为j
时的最大价值。
for (int i = 1; i <= n; ++i)
{
for (int j = 0; 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]);
}
}
和01背包二维dp类似,当前背包容量为 i 时所得最大价值,必不小于背包容量为 i - 1 时的最大价值,故先令此时背包容量的最大价值为 f[i - 1][j],再判断是否能放的下每个不同的物品,如果能放下,则比较后取较大的值。
一维dp解法:
与01背包一维dp类似,但是此处是顺序遍历。
为什么是顺序遍历呢?我们观察01背包和完全背包可以看到,01背包用的是上一层的,完全背包用的是当前层的,所以必须要从前往后遍历。
文末有模板。
多重背包
有 N 种物品和一个容量是 V 的背包。
第 i 种物品最多有 si 件,每件体积是 vi,价值是 wi。
求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
f[i,j] = max(f[i - 1][j - v[i] * k] + w[i] * k); k = 0, 1, 2, 3 ..., s[i]
跟01背包一样,主要是把多重拆成一重,文末有拆法的模板。
分组背包
给定N组物品和一个容量为V的背包。每组物品包含若干个,但在同一组内,你最多只能选择一件物品。每件物品有其对应的体积和价值。目标是选择一些物品放入背包,使得背包内物品的总体积不超过背包的容量,同时背包内物品的总价值尽可能大。
当第i组物品选0个也就是一个都不选的时候,其实就与从前i-1组物品选,且总体积不大于j的最大价值等价;
而当从第i组选择第k个物品时,可以由在前i-1组物品里选,总体积不大于j减去第i组的第k个物品的体积的最大价值再加上第i组的第k个物品的价值得到。
故可以得到状态转移方程为:
其实就是多次01背包,反复比较比max值就可以了。
下面有模板。
模板:
01背包模板
for(int i=1;i<=t;i++)
for(int j=m;j>=v[i];j--)
dp[j]=max(dp[j-v[i]]+w[i],dp[j]);//直接套01背包的板子
完全背包模板
for(int i=1;i<=t;i++)
for(int j=v[i];j<=m;j--)
dp[j]=max(dp[j-v[i]]+w[i],dp[j]);
多重背包模板
可以拆成完全背包问题,两种拆法
1. 一个一个拆
while(n--)
{
cin>>v>>w>>s;
while(s--)
{
a[++t]=v;
b[t]=w;
}
}
2. 二进制优化拆
for(int i=1,a,b,s;i<=n;i++)
{
cin>>a>>b>>s;
int k=1;
while(k<=s)//将每个物品都按照二进制合成
{
v[++cnt]=k*a;
w[cnt]=k*b;
s-=k;
k*=2;
}
if(s)
{
v[++cnt]=s*a;
w[cnt]=s*b;
}
}
然后调用01背包模板。
分组背包模板
可以看成01背包问题,对于每个物品,询问k次选不选,取最大的那个。
for (int i = 1; i <= n; ++i) // 遍历每一组物品
for (int j = m; j >= 0; --j) // 遍历背包容量从m到0
for (int k = 0; k < s[i]; ++k) // 遍历第i组中的每个物品
if (v[i][k] <= j) // 如果当前物品可以放入背包中
// 更新背包的最大价值,考虑放入当前物品或不放入的情况
f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);