动态规划之背包问题
背包模型
Dp{
状态表示{
集合{
表示的是所有选法
满足条件条件{
1.只从前i个物品中选
2.总体积<=j
}
}
属性 Min,Max,数量
}
状态计算 集合划分
}
01背包问题
n个物品和一个容量为v的背包,每一个物品体积vi,价值wi,每件物品只能用一次.
求背包能装的下的情况下能装下的最大价值为多少
我们考虑递推式,即状态转移方程。考虑第i件物品选或不选取决于选了之后手否能使价值变大;假设 我们已经选了第i-1件物品,那么我们第i件物品的选择便是:
dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])
(对于第i件物品,背包容量为j时,不选的值为dp[ i-1,j ] ,选的话为dp[i-1,j-v[i]]+w[i] ,此处的状态都在前面的状态一致的情况下得到)
再观察一下式子发现i-1甚至有点多余,因此我们呢可以对递推式做空间优化,即将dp数组优化成一维的:
for(int i=1;i<=n;i++)
for(int j=m;j>=v[i];j--){
dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
完全背包问题
每件物品可以用无限次,只要装得下。
状态转移方程代码:
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
for(int k=0;k*v[i]<=m;k++)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k*v[i]]+k*w[i])
我们考虑对其做优化,参考01背包,先对空间做优化,然后我们可以推导出
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
j
−
v
[
i
]
]
+
w
[
i
]
,
d
p
[
j
−
2
v
[
i
]
]
+
2
w
[
i
]
.
.
.
)
dp[j]=max(dp[j],dp[j-v[i]]+w[i],dp[j-2v[i]]+2w[i]...)
dp[j]=max(dp[j],dp[j−v[i]]+w[i],dp[j−2v[i]]+2w[i]...)
d p [ j − v [ i ] ] = m a x ( d p [ j − v [ i ] ] , d p [ j − 2 v [ i ] ] + w [ i ] . . . ) dp[j-v[i]]=max(dp[j-v[i]],dp[j-2v[i]]+w[i]...) dp[j−v[i]]=max(dp[j−v[i]],dp[j−2v[i]]+w[i]...)
对比上两个方程我们可以推导出:
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
j
−
v
[
i
]
]
+
w
[
i
]
)
dp[j]=max(dp[j],dp[j-v[i]]+w[i])
dp[j]=max(dp[j],dp[j−v[i]]+w[i])
实现代码:
for(int i=1;i<=n;i++)
for(int j=0;j<=m;j++)
dp[j]=max(dp[j],dp[j-v[i]]+w[i])
多重背包问 题
每件物品的数量有限制,各为 Si:
1.朴素版
核心思想:二进制优化;
我们将s[i]拆分成二进制表示的1,2…2k,其中2k<=s[i]/2
然后转化成01背包来做
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
]
[
j
−
k
⋅
v
[
i
]
]
+
k
⋅
w
[
i
]
)
dp[i][j]=max(dp[i-1][j],dp[i-1][j-k\cdot v[i]]+k\cdot w[i])
dp[i][j]=max(dp[i−1][j],dp[i−1][j−k⋅v[i]]+k⋅w[i])
其中k=0,1,2…s[i]
我们将dp数组的行进行压缩可以得到:
d
p
[
j
]
=
m
a
x
(
d
p
[
j
]
,
d
p
[
j
−
k
⋅
v
[
i
]
]
+
k
⋅
w
[
i
]
)
dp[j]=max(dp[j],dp[j-k\cdot v[i]]+k\cdot w[i])
dp[j]=max(dp[j],dp[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<=s[i];k++)
dp[j]=max(dp[j],dp[j-k*v[i]]+k*w[i])
2.二进制优化版
我们可以将每个物品的数量s做二进制优化,即将每一个s拆分成1,2,4…2k,s+1-2(k+1)。这样我们可以使用这些数表示任意1-s区间内的每一个数,并且时间复杂度也可以从原来的O(nms)优化到O(nmlogs)。然后我们呢对于拆分出来的物品用01背包的思路来做即可;
代码:
for(int i=1;i<=n;i++){ //二进制优化
int a,b,c;
cin >> a >> b >> c;
int u=1;
while(c>=u){
v[++cnt]=a*u;
w[cnt]=b*u;
c-=u;
u*=2;
}
if(c>0){
v[++cnt]=a*c;
w[cnt]=b*c;
}
}
n=cnt;
for(int i=1;i<=cnt;i++) //01背包
for(int j=m;j>=v[i];j--){
dp[j]=max(dp[j],dp[j=v[i]]+w[i]);
cout << dp[m] << endl;
}
分组背包问题
物品有n组,每一组有若干个,每一组最多选一个,求最大价值
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
1
,
j
−
v
[
i
]
[
k
]
]
+
w
[
i
]
[
k
]
)
dp[i][j]=max(dp[i-1][j],dp[i-1,j-v[i][k]]+w[i][k])
dp[i][j]=max(dp[i−1][j],dp[i−1,j−v[i][k]]+w[i][k])
因此本质上还是01背包问题,我们将其转化为01背包,只不过多一层循环k依次迭代更新每一组的各件物品;
for(int i=1;i<=n;i++)
for(int j=m;j>=0;j--)
for(int k=0;k<=s[i];k++)
if(j>=v[i][k])dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);