From《背包九讲》,稍作修改。
题目:
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
基本思路:
类似于01背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0件、取1件、取2件..等很多种。
01背包的状态转移方程:f[v]=max{f[v],f[v-k*c[i]]+k*w[i]}(0<=k*c[i]<=v),总复杂度超过O(VN)。
优化后的状态转移方程: f[v]=max{f[v],f[v-c[i]]+w[i]}
由01背包转换过来的解法延伸:
- 时间总复杂度较高,一般都会TLE。有个小小的地方优化,虽然大多数没有什么卵用:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。
- 一个常用的优化:利用二进制的思想将一件物品拆成多件物品。把第i种物品拆成费用为c[i]*2^k、价值为w[i]*2^k的若干件物品,其中k满足c[i]*2^k<=V。这是二进制的思想,因为不管最优策略选几件第i种物品,总可以表示成若干个2^k件物品的和。这样把每种物品拆成O(log(V/c[i]))件物品,是一个很大的改进。
- 中心思想:任何小于n的数都可以有{2^0,2^1,2^2,..2^k}该集合中的数加得。
- 例如1~13之间的任何一个数都可以用这些数加来,该集合为{ 2^0,2^1,2^2,2^3} ->{1,2,4,8},可以心算一下,从1-13这13个数都可以用这个集合里面的数相加得到,大大减少了枚举的数量。
最优化的代码模板:
for(int i=1;i<=n;i++)
{
for(int j=c[i];j<=V;j++)
{
f[j] = max(f[j],f[j-c[i]] + w[i]);
}
}
注意到第二个for是从c[i]枚举到V,目的是为了确保每件物品可取多件,从小的状态枚举到大的状态无非是保证该物品小状态里面可能取过。
模板到此为止,还需多做好题来练思维呐少年!
优哉游哉不是好ACMer!