洛谷 P1776 宝物筛选 题解
题目大意
现有一个最大载重为 \(W\) 的采集车,有 \(n\) 种物品,每种物品价值为 \(v_i\),重量为 \(w_i\),件数为 \(m_i\)。问在采集车不超重的前提下能装入采集车的物品价值和最大为多少。
朴素做法
对于 0-1 背包来说,其状态转移方程为 \(dp_{j}=\max(dp_{j},dp_{j-v_i}+w_i)\),其中 \(i\) 为物品,\(j\) 为背包容量。而现在我们可以在原来基础上再增加一层循环,遍历装入几件该物品。
时间复杂度 \(O(W\sum{m_i})\),预计得分 50pts,代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=105,maxW=4e4+10;
int n,W;
int v[N],w[N],m[N],dp[maxW];
int main(){
scanf("%d%d",&n,&W);
for (int i=1;i<=n;++i) scanf("%d%d%d",v+i,w+i,m+i);
for (int i=1;i<=n;++i){
for (int j=W;j>=w[i];--j){
for (int k=1;k<=m[i] && j>=k*w[i];++k) dp[j]=max(dp[j],dp[j-k*w[i]]+k*v[i]);
}
}
printf("%d",dp[W]);
return 0;
}
过渡
前一个思路无法再优化了。我们能否将多重背包转化为 0-1 背包呢?考虑拆物品。我们可以将第 \(i\) 件物品拆成 \(m_i\) 件价值 \(v_i\),重量 \(w_i\) 的物品,时间复杂度仍为 \(O(W\sum{m_i})\)。
二进制优化
注意到之前过渡做法会 TLE,主要原因是物品数量过多,有 \(\sum{m_i}\) 件,可不可以减少物品数量呢?由于每个数都对应一个二进制数,考虑拆成 \(2\) 的次方件。
对于当前物品数量 \(m_i\) 来说,若 \(p=\lfloor\log_2{m_i}\rfloor\),由于 \(m_i-1-2-\dots-2^{p-1}=m_i-2^p+1\) ,所以可以把此物品拆成 \(1,2,\dots,2^{p-1},m_i-2^p+1\)。如果装入 \(1\sim 2^p-1\) 件,都能用前 \(p-1\) 个元素表示出来。
若装入件数在 \(2^p\sim m_i\) 之间,因为 \(2^p\) 为小于 \(m_i\) 的最大的 \(2\) 的次方,所以 \(2^p\le m_i<2^{p+1}\),对于 \(m_i\in \mathbb{Z}\) 即 \(m_i\le 2^{p+1}-1\),移项可得 \(m_i-2^p+1\le 2^p\)。所以对于这一部分的件数,它们可以在 \(m_i-2^p+1\) 的基础上用前面的元素表示出来。
所以我们有了思路。遍历每个元素,先将其按上述方法拆分,再枚举个数套入背包模板,时间复杂度 \(O(W\sum(\log m_i))\)。代码如下。
#include<bits/stdc++.h>
using namespace std;
const int N=105,maxW=4e4+10;
int n,W;
int v[N],w[N],m[N],dp[maxW];
int main(){
scanf("%d%d",&n,&W);
for (int i=1;i<=n;++i) scanf("%d%d%d",v+i,w+i,m+i);
for (int i=1;i<=n;++i){
int p=0;
for (int j=2;j<=m[i];j*=2) ++p;
for (int j=(1<<(p-1));j>0;j>>=1){
for (int k=W;k>=j*w[i];--k) dp[k]=max(dp[k],dp[k-j*w[i]]+j*v[i]);
}
int q=m[i]-(1<<p)+1;
if (q!=0){
for (int j=W;j>=q*w[i];--j) dp[j]=max(dp[j],dp[j-q*w[i]]+q*v[i]);
}
}
printf("%d",dp[W]);
return 0;
}

浙公网安备 33010602011771号