【C++背包】稀奇古怪的多重背包问题

在这之前,我们讲到了01背包与完全背包。这次,我们来讲一讲很搞笑又稀奇古怪的(我不认为)多重背包问题。

多重背包问题

经典例题

例【n+2】这位流浪的探险者被困在了海岛上。有一天,他发现了一艘船,于是,他就乘坐这艘船准备回家。这时,他在岛上获取了N种东西,每件物品有n件真古怪哈,价值c,费用w,他需要让带回去的东西有更大的价值,所以,编程帮助他,找出最佳搭配。

感觉大家已经能够猜出这个悲催的人是谁了,就是大名鼎鼎的——鲁滨孙大人!!!

基本思路

这个题目不用说了吧,很简单,和完全背包基本一样。现在,我们只要把完全背包的方程略微改动即可。这种背包问题对于每种物品有n[i]+1(此处n为最多取物品的数量)种策略:取0个,1个,…n[i]个。令f[i][v]表示前i种物品恰放入容量为v的最大价值,则得出动态转移方程:
f ( i ) ( v ) = m a x ( f ( i − 1 ) ( v ) , f ( i − 1 ) ( v − k ∗ w ( i ) ) + k ∗ c ( i ) ∣ 0 < = k < = n ( i ) ) f(i)(v)=max(f(i-1)(v),f(i-1)(v-k*w(i))+k*c(i)|0<=k<=n(i)) f(i)(v)=max(f(i1)(v),f(i1)(vkw(i))+kc(i)0<=k<=n(i))此种算法时间复杂度为 O ( V ∗ ∑ n ( i ) ) O(V*∑n(i)) O(Vn(i))
结论得出的伪代码如下:

for(i=1...n)
	for(v=V...0)               //此地根据01写出,由于与完全不同,所以是V...0
		for(k=0...n[i])
		{
			if(v-k*v[i]<0) break;           //在for之前无法判断,只好在这里
			f[i][v]=max(f[i-1][v],f[i-1][v-k*w[i]]+k*c[i]);
		}

换一种好想好写的分析方法,将多重背包转换为01背包问题:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为 ( ∑ n ( i ) ) (∑n(i)) (n(i))的01背包问题,直接求解,时间复杂度依然是 ( O ( V ∗ ∑ n ( i ) ) ) (O(V*∑n(i))) O(Vn(i))

但是,我们要做一个懒人 勤快之人,期望将他转换成01背包问题之后就像上次完全背包问题一样降低复杂度。仍然考虑二进制的思想,把第i件物品换成若干件物品,使原问题中第i中物品可以取用的每种策略(取0~n[i]件物品)是与若干件物品代换后的相同。注意,取超过n[i]件物品的策略肯定不能出现。

于是,方法出现。将第i种物品换成n[i]件相同的物品,其中每件物品都有一个系数,这件物品的费用和价值均是原来的费用与价值乘上这个系数,使这些系数分别为1,2,4,···, 2 k − 1 2^{k-1} 2k1, n ( i ) − 2 k + 1 n(i)-2^k+1 n(i)2k+1,且k是满足 n ( i ) − 2 k + 1 > 0 n(i)-2^k+1>0 n(i)2k+1>0的最大整数(这里请注意,分出的数是可以组合出n[i]内所有数字)。

是不是感觉蒙了!炸了!
好,我们来举一个例子就可以明白了。总结上面的方法,假设n[i]=13,就将这种物品分成系数分别为1,2,4,6的四件物品(分成了四件一样但意义不同的物品,如果试试看,你就会发现,1,2,4,6可以组成13以内的任何数字。1,2,1+2,4,1+4,6,1+6,2+6,1+2+6,4+6,1+4+6,2+4+6,1+2+4+6=13)

对于上面有趣的式子,可以分0… 2 k − 1 2^{k-1} 2k1 2 k 2^k 2k…2n[i]讨论得出,希望读者自己思考思考,开动脑筋。

分成的这些物品系数的和为n[i],表示不可能取多于n[i]的第i件物品。这样就将第i件物品分为了 l o g ∗ n ( i ) log*n(i) logn(i)种物品,将原问题的复杂度转化为 O ( V ∗ ∑ l o g n ( i ) ) O(V*∑logn(i)) O(Vlogn(i))这是很大的改进呀!

分析结束,此处贴出慢版的程序:

#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
int v[10000],w[10000],s[10000],f[10000],n,m,i,j,k;
int main  ()
{
 cin>>n>>m;
 for (i=1; i<=n; i++)
   cin>>v[i]>>w[i]>>s[i];
 for (i=1; i<=n; i++)
   for (j=m; j>=0; j--)
     for (k=1; k<=s[i]; k++)
     {
   if (j-k*v[i]<0) break;
   f[j]=max(f[j-k*v[i]]+w[i]*k,f[j]);
  }
 cout<<f[m];
}

如有需要快版程序,请下载附带的资源包或发私信给我。


写文章不易,请大家多多支持,谢谢。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值