hdu 2191 悼念512汶川大地震遇难同胞——珍惜现在,感恩生活(二进制优化 多重背包)

http://acm.hdu.edu.cn/showproblem.php?pid=2191


多重背包。每种物品的数量有限。求背包所放物品的最大价值。本题没要求钱正好花完,所以初始化时全部初始化为0。

另一种好想好写的基本方法是转化为01背包求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为Σn[i]的01背包问题,直接求解,复杂度仍然是O(V*Σn[i])。

但是我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,每件物品只有一个。使得原问题中第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代换以后的物品。另外,取超过n[i]件的策略必不能出现。

方法是:将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为 1,2,4,...,2^(k-1),n[i]-2^k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种 物品分成系数分别为1,2,4,6的四件物品。

分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2^k..n[i]两段来分别讨论得出,并不难,希望你自己思考尝试一下。

这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为<math>O(V*Σlog n[i])的01背包问题,是很大的改进。


#include <stdio.h>
#include <string.h>
#include <algorithm>
using namespace std;

const int INF = 0x3f3f3f3f;
int n,m;
int c[110],w[110],num[110],dp[110];

void ZeroOnePack(int cost, int weight)
{
	for(int i = n; i >= cost; i--)
		dp[i] = max(dp[i],dp[i-cost]+weight);
}

void CompletePack(int cost, int weight)
{
	for(int i = cost; i <= n; i++)
		dp[i] = max(dp[i],dp[i-cost]+weight);

}

void MultiplePack(int cost, int weight, int amount)
{
	if(cost * amount >= n)//如果当前物品的花费*数量 >= 背包总容量,可以看成当前物品是无限的,完全背包解决
	{
		CompletePack(cost,weight);
	}
	else//否则,拆分该物品,变为01背包解决
	{
		int k = 1;
		while(k < amount)
		{
			ZeroOnePack(k*cost,k*weight);
			amount -= k;
			k = k << 1;
		}
		ZeroOnePack(amount*cost,amount*weight);
	}
}

int main()
{
	int test;
	scanf("%d",&test);
	while(test--)
	{
		scanf("%d %d",&n,&m);
		for(int i = 1; i <= m; i++)
		{
			scanf("%d %d %d",&c[i],&w[i],&num[i]);
		}

		memset(dp,0,sizeof(dp));
		//dp[0] = 0;

		for(int i = 1; i <= m; i++)
		{
			MultiplePack(c[i],w[i],num[i]);
		}
		printf("%d\n",dp[n]);
	}
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值