题目: https://www.acwing.com/problem/content/5/
题意: 裸多重背包,但是题目数据范围较大。三重循环纯朴素解法会超时。
思路:
- 为什么会超时? 对于每种物品,如果每次在遍历每种物品和每个体积的时候都枚举一遍物品数量,那么时间复杂度会到O(n^3),n=1000时,计算量就到了10亿次的级别,而c++一秒只能算1亿次,所以必须考虑优化。
- 怎么优化? 对于每个物品如果暴力枚举所有数量会超时,那么考虑将一部分物品分组。我们可以将物品按二进制的方式进行分组:1,2,4,8,,,2^k,C。前k+1项很好理解,分别是2的0~k次方,C是什么?因为我们要通过二进制分组的方式,表达出某个物品的任意数量,那么分组之后的总和也应该等于该物品的数量,C就是该物品的总数量依次减去前k+1项后的值。
- 为什么可以这样优化? 首先我们必须了解一点,二进制分组的方式可以表示出所有可能,比如:1,2,4,8。是可以表示出0~15任意的一个数的。 为什么要强调这一点?因为二进制分组同样遍历了所有的可能。 那么二进制和普通的枚举区别在哪? 二进制的遍历是在不同体积下体现的,而普通的枚举则是每种体积都会枚举一次。
概括而言:二进制将体积的变化与物品不同数量的枚举结合在了一起。
代码:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=25000,M=2000;
int n,m;
int v[N],w[N];
int f[M];
int main()
{
cin>>n>>m;
int cnt=0;//表示将数据处理为01背包问题后,总物品的个数
for(int i=1;i<=n;++i)
{
int a,b,c;
cin>>a>>b>>c;
int k=1;
while(k<=c)
{
cnt++;
v[cnt]=a*k;
w[cnt]=b*k;
c-=k;
k*=2;
}
if(c)
{
cnt++;
v[cnt]=a*c;
w[cnt]=b*c;
}
}
n=cnt;//总物品的数量变为cnt个
for(int i=1;i<=n;++i)
for(int j=m;j>=v[i];j--)
f[j]=max(f[j],f[j-v[i]]+w[i]);
cout<<f[m];
return 0;
}