多重背包定义
给定n种物品,其中第 i 种物品的体积为
w
i
w_i
wi,价值为
v
i
v_i
vi,并且有
c
i
c_i
ci个
有一容积为m的背包,要求选择若干个物品放入背包,使得物品的价值总和最大。
二进制优化
对多重背包问题一个朴素的思路是将第i种物品视作不同的
c
i
c_i
ci个物品,再做0/1背包,复杂度为
O
(
m
∑
i
=
1
n
c
i
)
O(m\sum_{i=1}^n c_i)
O(m∑i=1nci)
注意到任意正整数都可以表示为若干个2的幂次方之和
对第 i 种物品,我们并不用将
c
i
c_i
ci一一拆分,而可以将
c
i
c_i
ci按二的幂次方拆分
此时再做0/1背包,复杂度就变为
O
(
m
∑
i
=
1
n
log
c
i
)
O(m\sum_{i=1}^n{\log{c_i}})
O(m∑i=1nlogci)
const int maxn=1000010;
int n,m;
int val[maxn],wt[maxn];
int dp[maxn];
int cnt,ans;
int main()
{
n=read(); m=read();
for(int i=1;i<=n;i++)
{
int v=read(),w=read(),sum=read();
for(int j=1;j<=sum;j<<=1)
{
val[++cnt]=j*v; wt[cnt]=j*w;
sum-=j;
}
if(sum) val[++cnt]=sum*v,wt[cnt]=sum*w;
}
for(int i=1;i<=cnt;i++)
for(int j=m;j>=wt[i];j--)
dp[j]=max(dp[j],dp[j-wt[i]]+val[i]);
int ans=0;
for(int i=1;i<=m;++i)
ans=max(ans,dp[i]);
printf("%d",ans);
return 0;
}
单调队列优化
对于朴素的多重背包,其递推式为
d
p
[
j
]
=
m
a
x
(
d
p
[
j
−
k
∗
w
i
]
+
k
∗
v
i
)
,
1
≤
k
≤
c
i
dp[j]=max(dp[j-k*w_i]+k*v_i),\ 1\leq k \leq c_i
dp[j]=max(dp[j−k∗wi]+k∗vi), 1≤k≤ci
对于
d
p
[
j
]
dp[j]
dp[j],他会从
j
−
w
i
,
j
−
2
w
i
,
.
.
.
,
j
−
c
i
w
i
j-w_i, j-2w_i,...,j-c_iw_i
j−wi,j−2wi,...,j−ciwi转移
而对于
d
p
[
j
−
w
i
]
dp[j-w_i]
dp[j−wi],他会从
j
−
2
w
i
,
j
−
3
w
i
,
.
.
.
,
j
−
(
c
i
+
1
)
w
i
j-2w_i, j-3w_i,...,j-(c_i+1)w_i
j−2wi,j−3wi,...,j−(ci+1)wi转移
可以发现二者的转移仅有两处不同,如下图所示
这启发我们将
j
j
j按膜
w
i
w_i
wi分组,这样不同组的j转移的位置一定互不相同(也即不会相互转移)
对于膜
w
i
w_i
wi为u的一组,可得递推方程
d
p
[
u
+
k
w
i
]
=
m
a
x
(
d
p
[
u
+
t
w
i
]
+
(
k
−
t
)
v
i
)
,
k
−
c
i
≤
t
≤
k
−
1
dp[u+kw_i]=max(dp[u+tw_i]+(k-t)v_i),\ k-c_i\leq t \leq k-1
dp[u+kwi]=max(dp[u+twi]+(k−t)vi), k−ci≤t≤k−1
按照0/1背包的思路倒序枚举k,用单调队列维护
m
a
x
(
d
p
[
u
+
t
w
i
]
−
t
v
i
)
max(dp[u+tw_i]-tv_i)
max(dp[u+twi]−tvi)即可
循环计算
u
∈
[
0
,
w
i
)
u\in[0,w_i)
u∈[0,wi)的每一组即可计算得考虑前i种物品得dp[j]
const int maxn=500010;
int n,m;
int v[maxn],w[maxn],c[maxn];
int dp[maxn];
int q[maxn];
int calc(int i,int u,int k){
return dp[u+k*w[i]]-k*v[i];
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
v[i]=read();
w[i]=read();
c[i]=read();
}
for(int i=1;i<=n;++i)
{
for(int u=0;u<w[i];++u)
{
int ll=1,rr=1;
int mx=(m-u)/w[i]; // 上述推导u+kw中k得最大值
for(int k=mx-1;k>=max(mx-c[i],0);--k) // 将初始的转移位置加入单调队列
{
while(ll<rr && calc(i,u,q[rr-1])<=calc(i,u,k)) --rr;
q[rr++]=k;
}
for(int k=mx;k>=0;--k) // 倒序枚举每个状态
{
while(ll<rr&&q[ll]>k-1) ++ll;
int j=u+k*w[i];
if(ll<rr)dp[j]=max(dp[j],calc(i,u,q[ll])+k*v[i]);
if(k-c[i]-1>=0) // 加入新的转移位置
{
while(ll<rr && calc(i,u,q[rr-1])<=calc(i,u,k-c[i]-1)) --rr;
q[rr++]=k-c[i]-1;
}
}
}
}
int ans=0;
for(int i=1;i<=m;++i)
ans=max(ans,dp[i]);
printf("%d",ans);
return 0;
}