专辑:动态规划-背包-海量集训
分三次更新
更新提示:第三次更新
我们的第二次更新主要复习了前四种
背包问题主要分为以下几种:
1、01背包问题
2、完全背包问题
3、多重背包问题
4、混合三种背包问题
5、二维费用的背包问题
6、分组的背包问题
7、有依赖的背包问题
8、背包问题问法的变化
第二次更新主要讲解第5、6、8种背包的问题,依赖背包暂时不管
一、二维费用背包
题目描述:有N 件物品,放入第i 件物品耗费的两种空间代价是ci 和di,得到的价值是wi。两种代价可付出的最大值(两种背包容量)分别为V和U。求解将哪些物品装入背包可使价值总和最大。
分析:因为费用加了一维,我们只需要将数组多加一维便可,并没有什么太大的难度。
我们思考如何设置状态转移方程,我们玲f[i][j][k]表示选取了i种物品,分别用两个空间j和k来组成的最大价值。因为对于每种状态,都有两种情况,一种是取,一种是不取。对于不取的情况,我们可以从前一种物品的状态转移过来,即f[i][j][k]=f[i-1][j][k]。而对于取,就像于浪费了c[i]和d[i]个空间,就可以从用了前一个物品,浪费当前空前的状态转移过来,在加上放入这个物品所获得的利益。即f[i][j][k]=f[i-1][j-c[i]][k-d[i]]+w[i]
状态转移方程如下:
f
[
i
]
[
j
]
[
k
]
=
m
a
x
{
f
[
i
−
1
]
[
j
]
[
k
]
,
不取
f
[
i
−
1
]
[
j
−
c
[
i
]
]
[
k
−
d
[
i
]
]
+
w
[
i
]
,
取
f[i][j][k]=max \begin{cases} f[i-1][j][k], & \text{不取} \\ f[i-1][j-c[i]][k-d[i]]+w[i], & \text{取} \end{cases}
f[i][j][k]=max{f[i−1][j][k],f[i−1][j−c[i]][k−d[i]]+w[i],不取取
那么我们同样可以按之前说的方法来优化空间,把i给优化掉(详情见【海亮DAY12(二)】背包的补充一)。
状态转移方程如下:
f
[
j
]
[
k
]
=
m
a
x
{
f
[
j
−
1
]
[
k
]
,
不取
f
[
j
−
c
[
i
]
]
[
k
−
d
[
i
]
]
+
w
[
i
]
,
取
f[j][k] =max \begin{cases} f[j-1][k], & \text{不取} \\ f[j-c[i]][k-d[i]]+w[i], & \text{取} \end{cases}
f[j][k]=max{f[j−1][k],f[j−c[i]][k−d[i]]+w[i],不取取
二、分组背包
题目描述:有N 件物品和一个容量为V 的背包。第i 件物品的空间代价是ci,价值是wi。这些物品被划分为K 组,每组中的物品互相冲突,最多选一件。求解将哪些物品装入背包可使价值总和最大。
分析:我们令f[i][j]表示选取i件物品(i组物品)话费j的最大值。
我们相当于在01背包的基础上多加一重循环枚举组别内的物品,选取最优值即可
状态转移方程如下:
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
1
]
[
j
]
,
不取当前组的当前物品
f
[
i
−
1
]
[
j
−
c
[
k
]
]
+
w
[
k
]
,
取,物品k属于第i组
f[i][j]=max \begin{cases} f[i-1][j], & \text{不取当前组的当前物品} \\ f[i-1][j-c[k]]+w[k], & \text{取,物品k属于第i组} \end{cases}
f[i][j]=max{f[i−1][j],f[i−1][j−c[k]]+w[k],不取当前组的当前物品取,物品k属于第i组
同样的优化:
f
[
j
]
=
m
a
x
{
f
[
j
]
,
不取当前组的当前物品
f
[
j
−
c
[
k
]
]
+
w
[
k
]
,
取,物品k属于第i组
f[j]=max \begin{cases} f[j], & \text{不取当前组的当前物品} \\ f[j-c[k]]+w[k], & \text{取,物品k属于第i组} \end{cases}
f[j]=max{f[j],f[j−c[k]]+w[k],不取当前组的当前物品取,物品k属于第i组
给出一段代码:
for (int i=1;i<=n;i++)
for (int j=v;j>=0;j--)
for (int k=1;k<=x[i];k++)//x为第i组内物品的个数,即枚举物品
if (j-w[k][i]>=0)dp[j]=max(dp[j],dp[j-w[k][i]]+m[k][i]);
为方便理解,给出一道例题:
竞赛真理
题目描述:牛玉鑫在经历了无数次学科竞赛的失败以后,得到了一个真理:做一题就要对一题!但是要完全正确地做对一题是要花很多时间(包括调试时间),而竞赛的时间有限。所以开始做题之前最好先认真审题,估计一下每一题如果要完全正确地做出来所需要的时间,然后选择一些有把握的题目先做。 当然,如果做完了预先选择的题目之后还有时间,但是这些时间又不足以完全解决一道题目,应该把其他的题目用贪心之类的算法随便做做,争取“骗”一点分数。 问题求解: 根据每一题解题时间的估计值,确定一种做题方案(即哪些题目认真做,哪些题目“骗”分,哪些不做),使能在限定的时间内获得最高的得分。
分析:这道题就是裸的一道分组背包,不过不同的是,这里每一组内的个数是固定的,就只有两个——分别是正解和骗分(太强了%%%)。我们只需要用循环枚举每一组内的这两种情况,比较最优值.
那么具体代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[1000001]={};
int m[3][1001]={};//分值
int w[3][1001]={};//时间
int v,n;
int main(){
scanf("%d%d",&n,&v);
for (int i=1;i<=n;i++)
for (int j=1;j<=2;j++)
scanf("%d%d",&m[j][i],&w[j][i]);
for (int i=1;i<=n;i++)
for (int j=v;j>=0;j--)
for (int k=1;k<=2;k++)
if (j-w[k][i]>=0)dp[j]=max(dp[j],dp[j-w[k][i]]+m[k][i]);//分组背包,比较每个组的最优值
cout<<dp[v];
return 0;
}
三、背包问题问法的变化
1)背包问题问法的变化1:输出方案
分析:参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的。
以01背包为例,方程为
f [ i ] [ j ] = m a x { f [ i − 1 ] [ j ] f [ i − 1 ] [ j − c [ i ] ] + w [ i ] f[i][j]=max \begin{cases} f[i-1][j] \\ f[i-1][j-c[i]]+w[i] \end{cases} f[i][j]=max{f[i−1][j]f[i−1][j−c[i]]+w[i]
在设置一个g[i][j],g[i][j]为0时表示不取当前当前物品,反之则取
这样赋值: g [ i ] [ j ] = f [ i ] [ j ] = = f [ i − 1 ] [ j ] ? 0 : 1 g[i][j]=f[i][j]==f[i-1][j]?0:1 g[i][j]=f[i][j]==f[i−1][j]?0:1
输出方案伪代码如下(设最终状态为f [n][v ]):
while(n>0){//循环枚举
if (g[n][v] == 1) printf (”%d”, N);//说明取,则输出
v− = c[n];//减掉
n − −;
}
2)背包问题问法的变化1:输出字典序最小的方案
题目描述:以输出01背包最小字典序的方案为例。一般而言,求一个字典序最小的最优方案,只需要在转移时注意策略即可。
分析:因为要求字典序最小的方案,所以我们可以先把原编号反一反,把编号i=n-i+1.
原因仍然可以看我们1)的程序:
while(n>0){//循环枚举
if (g[n][v] == 1) printf (”%d”, N);//说明取,则输出
v− = c[n];//减掉
n − −;
}
由这个程序可以得知,我们需要倒叙输出方案,但是倒叙输出就有一个问题:先枚举到的一定是较大的,违背了字典序最小这一前提,所以这时我们就需要倒序输入,最后输出时在将编号归回原样即可
#####只是在输出方案时要注意,如果f [i][j] = f [i − 1][j]和f [i][j] = f [i − 1][v − ci] + wi都成立,那么必然要选物品i,因为只有选择才能满足最小字典序
3)背包问题问法的变化3:可行方案总数
分析:对于这类改变问法的问题,一般只需将状态转移方程中的max 改成sum 即可。这样做可行的原因在于状态转移方程已经考察了所有可能的背包组成方案。
例如若每件物品均是完全背包中的物品,转移方程即为:
f
[
i
]
[
j
]
=
s
u
m
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
c
i
]
)
f [i][j] = sum(f [i − 1][j], f [i][j − ci])
f[i][j]=sum(f[i−1][j],f[i][j−ci])
######初始条件是f [0][0] = 1。
4)背包问题问法的变化4:最优方案总数
分析:以01背包为例,f[i][j]表示最大值,g[i][j]表示方案总数
我们都知道01背包的转移方程:
f
[
i
]
[
j
]
=
m
a
x
{
f
[
i
−
1
]
[
j
]
f
[
i
−
1
]
[
j
−
c
[
i
]
]
+
w
[
i
]
f[i][j]=max \begin{cases} f[i-1][j] \\ f[i-1][j-c[i]]+w[i] \end{cases}
f[i][j]=max{f[i−1][j]f[i−1][j−c[i]]+w[i]
分两种情况讨论:
1、当f[i][j]==f[i-1][j]时,说明f[i][j]由f[i-1][j]转移过来,所以g[i][j]+=g[i-1][j]
2、当f[i][j]==f[i-1][j-c[i]]+w[i]时,说明f[i][j]由f[i-1][j-c[i]]转移过来,所以g[i][j]+=g[i-1][j-c[i]]
得到以下状态转移方程:
g
[
i
]
[
j
]
=
{
g
[
i
−
1
]
[
j
]
,
f[i][j]==f[i-1][j]
g
[
i
]
[
j
]
=
g
[
i
]
[
j
−
c
[
i
]
]
,
f[i][j]==f[i-1][j-c[i]]+w[i]
g[i][j]= \begin{cases} g[i-1][j], & \text{f[i][j]==f[i-1][j]} \\ g[i][j]=g[i][j-c[i]], & \text{f[i][j]==f[i-1][j-c[i]]+w[i]} \end{cases}
g[i][j]={g[i−1][j],g[i][j]=g[i][j−c[i]],f[i][j]==f[i-1][j]f[i][j]==f[i-1][j-c[i]]+w[i]
给出一段伪代码:
for(int i = 1, g[0][0] = 1; i <= N; i + +)
for(int j = 0; j <= V ; j + +) {
g[i][j] = 0; f [i][j] = max(f [i − 1][j], f [i − 1][j −c[i]] +w[i]);
if (f [i][j] == f [i − 1][j]) g[i][j]+ = g[i − 1][j];
else if (f [i][j] == f [i − 1][j − c[i]] + w[i])
g[i][j]+ = g[i − 1][j − c[i]];
}
第K忧解目前还在研究当中,先不附上了。
那么接下来我们对背包进行一个总结:
背包类型(已经贴了多遍了):
01背包问题
完全背包问题
多重背包问题
混合三种背包问题
二维费用的背包问题
分组的背包问题
有依赖的背包问题
背包问题问法的变化
对dp设计的一个总结:
1 划分阶段
按照时间或空间特征,有序的或者是可排序
2 确定状态和状态变量
可以表示各个阶段时,满足无后效性原则
3 确定决策并写出状态转移
根据相邻两段的各个
4 寻找边界条件
起始条件.终止条件