混合背包问题
问题重述
有 N 种物品和一个容量是 V 的背包。物品一共有三类:
第一类物品只能用1次(01背包);
第二类物品可以用无限次(完全背包);
第三类物品最多只能用 si 次(多重背包);
每种体积是 vi,价值是 wi。求解将哪些物品装入背包,可使物品体积总和不超过背包容量,且价值总和最大。输出最大价值。
输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品种数和背包容积。接下来有 N 行,每行三个整数 vi,wi,si,用空格隔开,分别表示第 i 种物品的体积、价值和数量。
si=−1 表示第 i 种物品只能用1次;
si=0 表示第 i 种物品可以用无限次;
si>0 表示第 i 种物品可以使用 si 次;
输出格式
输出一个整数,表示最大价值。
数据范围
0<N,V≤1000
0<vi,wi≤1000
−1≤si≤1000
思路分析:
在了解过01背包、完全背包和多重背包后,混合背包其实就是三者简单的结合。我们完全可以直接用老套路遍历物品数,在循环中判断物品的种类进而采用不同的处理方式。而多重背包的解法有多种我门可以根据数据范围选择合适的办法去处理。这里我们使用二进制的方法处理多重背包把它和01背包统一起来,最后我们在处理01和完全两种背包就行了。
C++代码:
#include<iostream>
#include<vector>
using namespace std;
struct Thing{//因为我们要将多重背包拆解重构成01背包在此定义结构体储存重构后的物品
int kind;//标志位标记物品的种类参照题目我们认为-1为01背包0为完全背包
int volume;
int value;
};
int N,V;
int main()
{
cin>>N>>V; //输入规模
vector<Thing>things;//定义重构后的物品容器
vector<int>ans;
ans.resize(V+10);//老规矩开辟合适空间的一维数组滚动储存答案一般多开几个
for( int i=0; i < N; i++ )//我们先将物品处理将其中多重背包化为01背包
{
int volume,value,counts;
cin>>volume>>value>>counts;//输入当前物品的体积、价值和种类
if(counts==-1||counts==0)//如果为-1或0说明为01背包或完全背包则不需处理直接收集进things容器
{
things.push_back({counts,volume,value});
}
else if(counts>0)//counts大于0说明为多重背包
{
for(int k=1;k<=counts;k*=2)//用二进制的方法将其拆解后收集
{
counts-=k;
things.push_back({-1,k*volume,k*value});//拆解后变为01背包但价值和体积与物品数量有关要重新构建
}
if(counts>0)
{
things.push_back({-1,counts*volume,counts*value});
}
}
}
for(auto thing : things)//重构后的物品就都变成了01背包和完全背包我们遍历物品分别处理
{
if(thing.kind<0)//如果小于0说明是01背包则体积从大到小遍历
{
for(int i=V;i>=thing.volume;i--)
{
ans[i]=max(ans[i],ans[i-thing.volume]+thing.value);
}
}
else//否则说明是完全背包则体积从小到大遍历
{
for(int i=thing.volume;i<=V;i++)
{
ans[i]=max(ans[i],ans[i-thing.volume]+thing.value);
}
}
}
cout<<ans[V];//输出最终答案
return 0;
}