问题描述:
一个旅行者随身携带一个背包,可以放入背包的物品有n种,每种物品的重量和价值分别是wi,viwi,vi,如果背包的最大容量限制是bb,每种物品可以放多个,怎样选择放入背包的物品以使得背包的价值最大?
1) 子问题定义:F[i][j]表示前i种物品中选取若干件物品放入剩余空间为j的背包中所能得到的最大价值。
2) 根据第i种物品放多少件进行决策
其中F[i-1][j-K*C[i]]+K*W[i]表示前i-1种物品中选取若干件物品放入剩余空间为j-K*C[i]的背包中所能得到的最大价值加上k件第i种物品;
设物品种数为N,背包容量为V,第i种物品体积为C[i],第i种物品价值为W[i]。
与01背包相同,完全背包也需要求出NV个状态F[i][j]。但是完全背包求F[i][j]时需要对k分别取0,…,j/C[i]求最大F[i][j]值,耗时为j/C[i]。那么总的时间复杂度为O(NV∑(j/C[i]))
伪代码如下:
F[0][] ← {0}
F[][0] ← {0}
for i←1 to N
do for j←1 to V
do for k←0 to j/C[i]
if(j >= k*C[i])
then F[i][k] ← max(F[i][k],F[i-1][j-k*C[i]]+k*W[i])
return F[N][V]
简单优化:
若两件物品满足C[i] ≤C[j]&&W[i] ≥W[j]时将第j种物品直接筛选掉。因为第i种物品比第j种物品物美价廉,用i替换j得到至少不会更差的方案。
这个筛选过程如下:先找出体积大于背包的物品直接筛掉一部分(也可能一种都筛不掉)复杂度O(N)。利用计数排序思想对剩下的物品体积进行排序,同时筛选出同体积且价值最大的物品留下,其余的都筛掉(这也可能一件都筛不掉)复杂度O(V)。
完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j 满足Ci ≤ Cj且Wi ≥ Wj,则将可以将物品j 直接去掉,不用考虑。这个优化的正确性是显然的:任何情况下都可将价值小费用高的j 换成物美价廉的i,得到的方案至少不会更差。对于随机生成的数据,这个方法往往会大大减少物品的件数,从而加快速度。然而这个并不能改善最坏情况的复杂度,因为有可能特别设计的数据可以一件物品也去不掉。
优化代码:
int main()
{
int n,w;
cin>>n>>w;
map<int,int > m;
int i,wi,vi;
for(i=0;i<n;i++)
{
cin>>wi>>vi;
if(wi<=w) // 费用大于w的去掉
{
if(m.find(wi)!=m.end())
{
if(m[wi]<vi) //费用(体积)相同时 选择更大的价值
{
m[wi]=vi;
}
}
else
{
m[wi]=vi;
}
}
}
map<int,int>::iterator it;
it=m.begin();
while(it!=m.end())
{
cout<<it->first<<" "<<it->second<<endl;
it++;
}
return 0;
}
转化为01背包
01 背包问题是最基本的背包问题,我们可以考虑把完全背包问题转化为01 背包问题来解。
最简单的想法是,考虑到第i 种物品最多选⌊V /Ci⌋ 件,于是可以把第i 种物品转化为⌊V /Ci⌋ 件费用及价值均不变的物品,然后求解这个01 背包问题。这样的做法完全没有改进时间复杂度,但这种方法也指明了将完全背包问题转化为01 背包问题的思路:将一种物品拆成多件只能选0 件或1 件的01 背包中的物品。更高效的转化方法是:把第i 种物品拆成费用Ci * 2^k、价值为Wi * 2^k 的若干件物品,其中k 取遍满足Ci*2^k ≤ V 的非负整数。这是二进制的思想。因为,不管最优策略选几件第i 种物品,其件数写成二进制后,总可以表示成若干个2^k 件物品的和。这样一来就把每种物品拆成O(log ⌊V /Ci⌋) 件物品,是一个很大的改进。
代码如下:
int main()
{
int n;
cin>>n;
while(n--)
{
int money;
cin>>money;
int i;
for(i=0;i<3;i++)
{
for(int j=0;j<=money;j++)
{
if(j>=v[i])
{
dp_1[j]=max(dp_1[j],dp_1[j-v[i]]+v[i]);
}
}
}
cout<<money-dp_1[money]<<endl;
}
return 0;
}
完全背包的思想和01背包的思想非常相似,两者的区别就在01背包是一个物体只有一个,而完全背包的物体是多个的。如果能把这一点给搞懂就很好理解了。
01背包具体参考这篇博客