HDU 2191 悼念512汶川大地震遇难同胞――珍惜现在,感恩生活 (多重背包)

多重背包问题(这里摘用崔添翼著作《背包九讲》里的内容)

题目

有 N 种物品和一个容量为 V 的背包。第 i 种物品最多有 Mi 件可用,每件耗费的空间是Ci,价值是Wi。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超过背包容量,且价值总和最大。
基本算法
这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改即可。因为对于第 i 种物品有 Mi + 1 种策略:取 0 件,取 1 件……取 Mi 件。令 F[i,v] 表示前 i 种物品恰放入一个容量为 v 的背包的最大价值,则有状态转移方程:

F[i,v]=max{F[i-1,v-k*Ci]+k*Wi|0 ≤k ≤Mi}

复杂度是 O(VΣMi)。
转化为 01 背包问题

另一种好想好写的基本方法是转化为01背包求解:把第 i 种物品换成Mi件01背包中的物品,则得到了物品数为ΣMi的01背包问题。直接求解之,复杂度仍然是O(VΣMi)。但是我们期望将它转化为01背包问题之后,能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,使得原问题中第i种物品可取的每种策略——取0...Mi件——均能等价于取若干件代换以后的物品。另外,取超过Mi件的策略必不能出现。 方法是:将第 i 种物品分成若干件 01 背包中的物品,其中每件物品有一个系数。这件物品的费用和价值均是原来的费用和价值乘以这个系数。令这些系数分别为 1,2,2^2 ...2^k−1,Mi −2^k + 1,且 k 是满足 Mi −2^k +1 > 0 的最大整数。例如,如果 Mi 为 13,则相应的 k = 3,这种最多取 13 件的物品应被分成系数分别为 1,2,4,6 的四件物品。分成的这几件物品的系数和为 Mi,表明不可能取多于 Mi 件的第 i 种物品。另外这种方法也能保证对于0...Mi 间的每一个整数,均可以用若干个系数的和表示。这里算法正确性的证明可以分 0...2^k−1 和 2^k ...Mi 两段来分别讨论得出,希望读者自己思考尝试一下。 这样就将第 i 种物品分成了 O(logMi) 种物品,将原问题转化为了复杂度为 O(VΣlogMi) 的 01 背包问题,是很大的改进。

下面给出 O(logM) 时间处理一件多重背包中物品的过程:

def MultiplePack(F,C,W,M)
if C ·M ≥V 
	CompletePack(F,C,W)
	return 
k ←1
while k < M 
	ZeroOnePack(kC,kW) 
	M ←M - k 
	k ←2k 
ZeroOnePack(C ·M,W ·M) 


希望你仔细体会这个伪代码,如果不太理解的话,不妨翻译成程序代码以后,单步执行几次,或者头脑加纸笔模拟一下,以加深理解。
 

Description

急!灾区的食物依然短缺! 
为了挽救灾区同胞的生命,心系灾区同胞的CK准备自己采购一些粮食支援灾区,现在假设CK一共有资金n元,而市场有m种大米,每种大米都是袋装产品,其价格不等,并且只能整袋购买。 
请问:CK能用有限的资金最多能采购多少公斤粮食呢? 

Input

输入数据首先包含一个正整数C,表示有C组测试用例,每组测试用例的第一行是两个整数n和m(1<=n<=100, 1<=m<=100),分别表示经费的金额和大米的种类,然后是m行数据,每行包含3个数p,h和c(1<=p<=20,1<=h<=200,1<=c<=20),分别表示每袋的价格、每袋的重量以及对应种类大米的袋数。

Output

对于每组测试数据,请输出能够购买大米的最多重量,你可以假设经费买不光所有的大米,并且经费你可以不用完。每个实例的输出占一行。

Sample Input

1
8 2
2 100 4
4 100 2

Sample Output

400

代码

#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
#define MAXN 110
int p[MAXN],w[MAXN],n[MAXN];//price weight num
int dp[MAXN];
int main(){
	int C,a,b;//a for total money    b for varieties
	cin>>C;
	while(C--){
		cin>>a>>b;
		for(int i=0;i<b;i++) cin>>p[i]>>w[i]>>n[i];
		memset(dp,0,sizeof(dp));
		for(int i=0;i<b;i++){
			int num=n[i];//取第i件物品的数量 
			for(int k=1;num>0;k<<=1){//采用二进制01背包解法 
				int t=min(k,num);//num与t的关系这里不详细说明,自己去推一下 
				for(int j=a;j>=p[i]*t;j--)//倒序同样为了防止重复购买 
					dp[j]=max(dp[j],dp[j-p[i]*t]+w[i]*t);
				num-=t;
			}
		}
		cout<<dp[a]<<endl;
	}
	return 0;
} 

猜你喜欢

转载自blog.csdn.net/HPU_LY/article/details/81538187
今日推荐