【背包专题汇总】

本文详细介绍了背包问题的三种类型:01背包、完全背包和多重背包。针对每种类型,阐述了问题描述、状态转移方程、初始化设置以及实际题目求解策略。01背包中,状态F[i,v]表示前i件物品恰放入容量为v的背包可以获得的最大价值,而完全背包和多重背包则考虑了每种物品的数量限制。" 121395781,10983889,Linux环境下Samba服务配置与使用教程,"['Linux', 'Samba服务', '文件共享', '系统管理']
摘要由CSDN通过智能技术生成


【01背包】

1.问题描述

有 件物品和一个容量为 的背包。放入第 件物品耗费的费用是 Ci,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。

这是最基础的背包问题,特点是:每种物品仅有一件,可以选择放或不放。

用子问题定义状态:即 F[i,v] 表示前 件物品恰放入一个容量为 的背包可以获得的最大价值。则其状态转移方程便是:

F[i,v]= max{F[i− 1,v],F[i− 1,v− Ci]+ Wi}

这个方程非常重要,基本上所有跟背包相关的问题的方程都是由它衍生出来的。所以有必要将它详细解释一下:“将前 件物品放入容量为 v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只和前− 1 件物品相关的问题。如果不放第件物品,那么问题就转化为“前− 1 件物品放入容量为的背包中”,价值为 F[i− 1,v];如果放第i件物品,那么问题就转化为“前 − 1 件物品放入剩下的容量为v− C的背包中”,此时能获得的最大价值就是F[− 1,vCi] 再加上通过放入第 件物品获得的价值Wi


2.初始化:

有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满

一种区别这两种问法的实现方法是在初始化的时候有所不同


如果是第一种问法,要求恰好装满背包,那么在初始化时

除了 F[0] 为 0,其它F[1..V ] 均设为 -∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。


如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0..V ]全部设为 0。


3.公式一般形式:


1)非状态压缩方程:

F[0,0..V] ← 0
for i ←1 to N for v ← Ci toV
F[i,v] ← max{F[i ? 1,v],F[i? 1,v? Ci] + Wi}

2)进行空间优化的状态压缩方程

F[0..V ]←0
for i← 1 to N forv ← V to Ci
F[v] ← max{F[v],F[v? Ci]+ Wi}

第二层循环逆序的原因:

注意一点,我们是由第 i - 1 次循环的两个状态推出 第 i 个状态的,而且 v  > v - weight[i],则对于第i次循环,背包容量只有当V..0循环时,才会先处理背包容量为v的状况,后处理背包容量为 v-weight[i] 的情况。

具体来说,由于,在执行v时,还没执行到v - weight[i]的,因此,f[v - weight[i]]保存的还是第i - 1次循环的结果。即在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,此时f[v-weight[i]]存储的是f[i - 1][v-weight[i]]。

相反,如果在执行第 i 次循环时,背包容量按照0..V的顺序遍历一遍,来检测第 i 件物品是否能放。此时在执行第i次循环 且 背包容量为v时,此时的f[v]存储的是 f[i - 1][v] ,但是,此时f[v-weight[i]]存储的是f[i][v-weight[i]]。

因为,v  > v - weight[i],第i次循环中,执行背包容量为v时,容量为v - weight[i]的背包已经计算过,即f[v - weight[i]]中存储的是f[i][v - weight[i]]。即,对于01背包,按照增序枚举背包容量是不对的。


4.实际题目求解:

hdu 2602 bone collector(01背包)

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

分析:标准的不必装满的01,背包....裸裸的贴上去代码~

Bone Collector

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 48124    Accepted Submission(s): 20074


Problem Description
Many years ago , in Teddy’s hometown there was a man who was called “Bone Collector”. This man like to collect varies of bones , such as dog’s , cow’s , also he went to the grave …
The bone collector had a big bag with a volume of V ,and along his trip of collecting there are a lot of bones , obviously , different bone has different value and different volume, now given the each bone’s value along his trip , can you calculate out the maximum of the total value the bone collector can get ?

 

Input
The first line contain a integer T , the number of cases.
Followed by T cases , each case three lines , the first line contain two integer N , V, (N <= 1000 , V <= 1000 )representing the number of bones and the volume of his bag. And the second line contain N integers representing the value of each bone. The third line contain N integers representing the volume of each bone.
 

Output
One integer per line representing the maximum of the total value (this number will be less than 2 31).
 

Sample Input
  
  
1 5 10 1 2 3 4 5 5 4 3 2 1
 

Sample Output
  
  
14
 


hdu 2546:饭卡(01背包)

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

饭卡

Time Limit: 5000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 21034    Accepted Submission(s): 7336


Problem Description
电子科大本部食堂的饭卡有一种很诡异的设计,即在购买之前判断余额。如果购买一个商品之前,卡上的剩余金额大于或等于5元,就一定可以购买成功(即使购买后卡上余额为负),否则无法购买(即使金额足够)。所以大家都希望尽量使卡上的余额最少。
某天,食堂中有n种菜出售,每种菜可购买一次。已知每种菜的价格以及卡上的余额,问最少可使卡上的余额为多少。
 

Input
多组数据。对于每组数据:
第一行为正整数n,表示菜的数量。n<=1000。
第二行包括n个正整数,表示每种菜的价格。价格不超过50。
第三行包括一个正整数m,表示卡上的余额。m<=1000。

n=0表示数据结束。
 

Output
对于每组输入,输出一行,包含一个整数,表示卡上可能的最小余额。
 

Sample Input
  
  
1 50 5 10 1 2 3 2 1 1 2 3 2 1 50 0
 

Sample Output
  
  
-45 32
 
/*
余额里的钱如果小于5元直接输出 
如果大于5元,取出5元用来买最贵的菜,
剩余钱就是
利用0,1背包求最优解
*/ 
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;

int dp[1003];
int w[1003]; 
int main()
{
	int V,n;
	while(cin>>n,n)
	{
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			cin>>w[i];
		}
		cin>>V;
	
		sort(w+1,w+1+n);
	
		if(V<5)
		{
			cout<<V<<endl;
			continue;
		}
		V-=5;
	
//最后剩余的5元用来吗买最贵的菜 
//状态压缩的写法; 
	for(int i=1;i<n;i++)
	for(int v=V;v>=w[i];v--)
	{
		dp[v]=max(dp[v],dp[v-w[i]]+w[i]);
	 } 
	 
	 cout<<V+5-w[n]-dp[V]<<endl;
		
	}
	
	return 0;
 } 


【完全背包】

1.问题描述

这个问题非常类似于 01 背包问题,所不同的是每种物品有无限件。也就是从每种物品的角度考虑,与它相关的策略已并非取或不取两种,而是有取0 件、取 1 件、取 2 件……直至取 ⌊V/Ci⌋件等许多种。

2.初始化:

有的题目要求“恰好装满背包”时的最优解,有的题目则并没有要求必须把背包装满

一种区别这两种问法的实现方法是在初始化的时候有所不同


如果是第一种问法,要求恰好装满背包,那么在初始化时

除了 F[0] 为 0,其它F[1..V ] 均设为 -∞,这样就可以保证最终得到的 F[V ] 是一种恰好装满背包的最优解。


如果并没有要求必须把背包装满,而是只希望价格尽量大,初始化时应该将 F[0..V ]全部设为 0。


3.公式一般形式:

1)动态转移方程:
F[i,v] = max{F[i − 1,v−kCi] +kWi | 0 ≤ kCi ≤v}

2)

F[0..V ]←0

for ← 1 to 

for ← Cto V

F[v] ← max(F[v],F[vCi]+ Wi)

这里只有第二层for循环和01背包不同,why?

首先想想为什么 01 背包中要按照v递减的次序来循环。

v 递减是为了保证第i次循环中的状态F[i;v]是由状态F[i-1;v-Ci]递推而来。
换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第
i 件物品”这件策
略时,依据的是一个绝无已经选入第
i 件物品的子结果F[i-1;v-Ci]。而现在完全背
包的特点恰是每种物品可选无限件,所以在考虑“加选一件第
i 种物品”这种策略时,
却正需要一个可能已选入第
i 种物品的子结果F[i;v-Ci],所以就可以并且必须采用v
递增的顺序循环。这就是这个简单的程序为何成立的道理。


4.实际题目求解

Problem Description
  对于吃货来说,过年最幸福的事就是吃了,没有之一!
  但是对于女生来说,卡路里(热量)是天敌啊!
  资深美女湫湫深谙“胖来如山倒,胖去如抽丝”的道理,所以她希望你能帮忙制定一个食谱,能使她吃得开心的同时,不会制造太多的天敌。

  当然,为了方便你制作食谱,湫湫给了你每日食物清单,上面描述了当天她想吃的每种食物能带给她的幸福程度,以及会增加的卡路里量。
 

Input
  输入包含多组测试用例。
  每组数据以一个整数n开始,表示每天的食物清单有n种食物。 
  接下来n行,每行两个整数a和b,其中a表示这种食物可以带给湫湫的幸福值(数值越大,越幸福),b表示湫湫吃这种食物会吸收的卡路里量。
  最后是一个整数m,表示湫湫一天吸收的卡路里不能超过m。

   [Technical Specification]
  1. 1 <= n <= 100
  2. 0 <= a,b <= 100000
  3. 1 <= m <= 100000
 

Output
  对每份清单,输出一个整数,即满足卡路里吸收量的同时,湫湫可获得的最大幸福值。
 

Sample Input
  
  
3 3 3 7 7 9 9 10 5 1 1 5 3 10 3 6 8 7 5 6
 

Sample Output
  
  
10 20

不必装满的裸裸的完全背包

#include<iostream>
#include<cstring>

using namespace std;

int c[103];
int w[103];
int dp[100005];
int main()
{
	int n;
	while(cin>>n)
	{
		memset(dp,0,sizeof(dp));
		for(int i=1;i<=n;i++)
		{
			cin>>w[i]>>c[i];
		}
		
		int V;
		cin>>V;
		
		for(int i=1;i<=n;i++)
		for(int v=c[i];v<=V;v++) 
		{
			dp[v]=max(dp[v],dp[v-c[i]]+w[i]);
		}
		
		cout<<dp[V]<<endl;
	}	
	return 0;
 } 

3 多重背包问题

3.1 题目
N 种物品和一个容量为 V 的背包。第 i 种物品最多有 M i 件可用,每件耗费的
空间是
C i ,价值是 W i 。求解将哪些物品装入背包可使这些物品的耗费的空间总和不超
过背包容量,且价值总和最大。

3.2 基本算法


这题目和完全背包问题很类似。基本的方程只需将完全背包问题的方程略微一改
即可。
因为对于第
i 种物品有 M i + 1 种策略:取 0 件,取 1 件……取 M i 件。令 F [ i; v ]
表示前 i 种物品恰放入一个容量为 v 的背包的最大价值,则有状态转移方程:
F[iv] =maxfF[i-1; v-k Ci] +kWij0kMi g
复杂度是 O ( V Σ M i )


3.3  转化为01 背包问题

另一种好想好写的基本方法是转化为 01 背包求解:把第 种物品换成 M件 01 背包中的物品,则得到了物品数为ΣMi的 01 背包问题。直接求解之,复杂度仍然是

O(ΣMi)。

3.4  多重 背包便准代码

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

3.5 不考虑实际价值值考虑体积的多重 背包标准代码

F[0,1...V] ← −1

F[0,0] ← 0

for ← 1 to for ← 0 to V

if F[− 1][j] ≥ 0

F[i][j]= Melse

F[i][j]= −1 for ←0 to VCi

if F[i][j]0

F[i][jCi] ← max{F[i][j+Ci],F[i][j] − 1} 最终F[N][0...V] 便是多重背包可行性问题的答案。



贴一道裸题:

悼念512汶川大地震遇难同胞——珍惜现在,感恩生活

Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 25710    Accepted Submission(s): 10869


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

后记:
人生是一个充满了变数的生命过程,天灾、人祸、病痛是我们生命历程中不可预知的威胁。
月有阴晴圆缺,人有旦夕祸福,未来对于我们而言是一个未知数。那么,我们要做的就应该是珍惜现在,感恩生活——
感谢父母,他们给予我们生命,抚养我们成人;
感谢老师,他们授给我们知识,教我们做人
感谢朋友,他们让我们感受到世界的温暖;
感谢对手,他们令我们不断进取、努力。 
同样,我们也要感谢痛苦与艰辛带给我们的财富~


 

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

//f[i][v]=max(f[i-1][v],f[i-1][v-kc[i]]+k*w[i])  0<=k<=m[i]
//m为每件物体数量数组
//c为体积数组
//w为价值数组 

//将多重背包看成01背包,时间复杂度稍微较高
 
#include<iostream>
#include<cstring>
using namespace std;
int V;
int c[101];
int m[101];
int w[101];
int dp[201];
int n;


int main()
{
	int t;
	cin>>t;
	while(t--)
	{
		memset(dp,0,sizeof(dp));
		cin>>V>>n;
		
		for(int i=1;i<=n;i++)
		{
			cin>>c[i]>>w[i]>>m[i];
		}
		
		for(int i=1;i<=n;i++)
			for(int k=1;k<=m[i];k++)//特别注意这儿k是从1:m[i] 
				for(int v=V;v>=c[i];v--)
				
				{
					dp[v]=max(dp[v],dp[v-c[i]]+w[i]);
				}
				
		cout<<dp[V]<<endl;
	}
	
	return 0;
}
多重背包标准代码:
<pre name="code" class="cpp">#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;
const int MAXN=110;

int dp[MAXN];
int value[MAXN];//每袋的价格
int weight[MAXN];//每袋的重量
int bag[MAXN];//袋数
int nValue,nKind;

//0-1背包,代价为cost,获得的价值为weight
void ZeroOnePack(int cost,int weight)
{
    for(int i=nValue;i>=cost;i--)
      dp[i]=max(dp[i],dp[i-cost]+weight);
}

//完全背包,代价为cost,获得的价值为weight
void CompletePack(int cost,int weight)
{
    for(int i=cost;i<=nValue;i++)
      dp[i]=max(dp[i],dp[i-cost]+weight);
}

//多重背包
void MultiplePack(int cost,int weight,int amount)
{
    if(cost*amount>=nValue) CompletePack(cost,weight);
    else
    {
        int k=1;
        while(k<amount)
        {
            ZeroOnePack(k*cost,k*weight);
            amount-=k;
            k<<=1;
        }
        ZeroOnePack(amount*cost,amount*weight);//这个不要忘记了,经常掉了
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        //这个dp的初始化一定不要忘记,可以不装满则初始化为0,
        //否则dp[0]=0,其余的为-INF
        memset(dp,0,sizeof(dp));
        scanf("%d%d",&nValue,&nKind);
        for(int i=0;i<nKind;i++)
          scanf("%d%d%d",&value[i],&weight[i],&bag[i]);
        for(int i=0;i<nKind;i++)
          MultiplePack(value[i],weight[i],bag[i]);
        printf("%d\n",dp[nValue]);
    }
    return 0;
}


 多重部分和问题 
有n种不同大小的数字ai,每种个mi个。判断是否可以从这些数字之中选出若干使他们的和恰好为k。
n<=100;   1<=ai,mi<=100000;1<=k<=100000;
dp[i][j]代表用前i个数字凑出的和为j时剩余的ai的个数,如果凑不出来则为-1.
#include<iostream>
#include<cstring>
#include<algorithm>
using namespace std;


int n,a[110],m[110],k;
int dp[110000];//初始化的值应为没有任何一个数字可以被选中时的值 

int main()
{
	while(cin>>n)
	{
		memset(dp,-1,sizeof(dp));
		dp[0]=0;
		
		for(int i=1;i<=n;i++)cin>>a[i];
		for(int i=1;i<=n;i++)cin>>m[i];
		cin>>k;
		for(int i=1;i<=n;i++)
		{
			for(int j=0;j<=k;j++)
			{
				if(dp[j]>=0)dp[j]=m[i];
				else if(j<a[i]||dp[j-a[i]]<=0)dp[j]=-1;
				else dp[j]=dp[j-a[i]]-1;
			}
		 } 
		 if(dp[k]>=0)cout<<"yes"<<endl;
		 else cout<<"no"<<endl;
		
	}
	
	return 0;
 } 




  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值