01背包,完全背包,混合背包详解

01背包,完全背包,多重背包详解

01背包

01背包就是给n个物体,每个物体都有代价w[i] 和 价值 v[i];

给定一个容量为t的背包,每个物体只能选一次装入背包,所能得到的最大价值是多少

动态规划就是找状态转移方程,

dp[ i ] [ j ]=max ( dp [ i - 1 ] [ j ] , dp [ i -1 ] [ j - w [ i ] ]+ v [ i ] )

dp[ i ] [ j ] 表示从前 i 个物品中挑选物品放入容量为 j 的背包可以获得的最大价值。

就是在当前状态有两种选择;

  1. 不选择当前物品 :即当前的i个物品在容量为j的背包里的最大价值就等于前i-1个物品在容量为j的背包里的最大价值 dp[i] [j]=dp[i-1] [j]

  2. 选择当前物品 :那当前的i个物品在容量为j的背包里的最大价值就等于前i-1个物品在容量为j-w[i]的背包里的最大价值加上当前物品的价值 dp[ i ] [ j ]=d[ i - 1 ] [ j - w[ i ] ]+v[ i ]

代码

for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
for(int j=1;j<=t;j++){
    if (j < w[i])
		 dp[i][j] = dp[i - 1][j];
	else
         dp[i][j]=max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);
}
}

将二维优化为一维

dp[ i ] [ j ]=max ( dp [ i - 1 ] [ j ] , dp [ i -1 ] [ j - w [ i ] ]+ v [ i ] )

从上式中可以看出dp[i] []只与dp[i-1] [] 有关,所以可以进行优化为一维

dp [ j ]=max ( dp [ j ] , dp [ j - w [ i ] ]+ v [ i ] )

for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
for(int j=t;j>=w[i];j--){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}

逆序是因为每一个物品只能选一次,如果正序遍历,则前面的值将有可能被修改掉,从而导致后面(dp [j-w[i]], j-w[i] 等于前面改变的那个值)要用到这个值的数据的错误;相反如果逆序遍历,先修改后面的数据再修改前面的数据,此种情况就不会出错了;这个时候就可以联系到完全背包了,因为完全背包是可以选无限次,可以不断修改,所以就是正向了。

初始化

  1. 背包需完全填满

    那么在初始化时除了dp[0]为0其它均设为- ∞ \infty (- ∞ \infty 即没有方案满足要求)这样就可以保证最终得到的是一种恰好装满背包的最优解。那么这是为什么呢,如果最终有方案满足背包完全填满,那么dp[0]就存在,初始化dp数组事实上就是在没有任何物品可以放入背包时的合法状态。如果要求背包恰好装满,那么此时只有容量为0的背包可能被价值为0的物品恰好装满”,所以其它容量的背包均没有合法的解,属于未定义的状态,它们的值就都应该是- ∞ \infty 了。

  2. 背包不需完全填满

    如果背包并非必须被装满,那么任何容量的背包都有一个合法解“什么都不装”,这个解的价值为0,所以初始时 状态的值也就全部为0了

回溯找最优解选了哪些物品

  1. dp[i] [j]=dp[i-1] [j] 时,说明没有选择第i 个商品,则回到dp[i-1] [j];
    2. dp[i] [j]=dp[i-1] [j-w[i]]+v[i]时,说明装了第i个商品,该商品是最优解组成的一部分,随后我们得回到装该商品之前,即回到dp[i-1] [j-w[i]] 的位置。
       3. 一直遍历到i=0结束为止,所有解的组成都会找到。

< https://www.luogu.com.cn/problem/P1048 > 采药 模板题

完全背包

有n种物品和一个容量为t的背包,每一种物品都有无限件可用。每一种物品都有代价w[i]和价值v[i]。求解将哪些物品放入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

两种状态转移方程

  1. dp[i] [j]=max( dp[i-1] [j] , dp[i-1] [j-k × \times ×w[i]]+k × \times ×v[i];
  2. dp[i] [j]=max(dp[i-1] [j],dp[i-1] [j-w[i]]+v[i] ) //优化后

完全背包样式和01背包的很相似,唯一不同的是对V遍历时变为正序,而01背包为逆序。01背包中逆序是因为dp[i] []只和dp[i-1] []有关,且第i件的物品加入不会对dp[i-1] [ ]状态造成影响。而完全背包则考虑的是第i种物品的出现的问题,第i种物品一旦出现它势必应该对第i种物品还没出现的各状态造成影响。也就是说,原来没有第i种物品的情况下可能有一个最优解,现在第i种物品出现了,而它的加入有可能得到更优解,所以之前的状态需要进行改变,故需要正序。

for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
for(int j=w[i];j<=t;j++){
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
}
}

< https://www.luogu.com.cn/problem/P1616 > 疯狂的采药 模板题

多重背包

有n种物品和一个容量为t的背包,第i种物品最多有n[i]件可用,每件费用是w[i],价值是v[i]。求解将哪些物品装入背包价值最大。

多重背包和完全背包又很相似,一个是选无限次,一个是选有限次。

状态转移方程

dp[i] [j] = max(dp[i-1] [j] , dp[i-1] [j-k × \times ×w[i]]+k × \times ×v[i]); (0=<k<=n[i])

直接用这个状态转移方程时间复杂度就有点高了 (t × \times × ∑ n [ i ] \sum n[i] n[i]

我们可以对其进行优化,其实也挺好想,就是将它拆成01背包来求,比如说第2种物品有3件,那我们可以把这三件物品当成不同的物品放入01背包。但是又有一个问题,那就是怎么拆。一个一个的拆?那样时间复杂度显然没变还是(t × \times × ∑ n [ i ] \sum n[i] n[i]) 。

正确的拆法是用二进制来拆,具体看代码


	int cnt=1;
	cin>>n>>t;
	for(int i=1;i<=n;i++){
		scanf("%d%d%d",&a,&b,&s);//a表示当前物品的代价,b表示价值,s表示数量
		int m=1;
		while(m<=s){
			s-=m;
			w[cnt]=m*a,v[cnt++]=m*b;//分成用每次乘二表示的数打包成一件物品成一项加入数组
			m*=2;
		}
		if(s>0){
			w[cnt]=s*a,v[cnt++]=s*b;//剩下的单成以项,加入数组
		}
	}
//下面就是01背包
	for(int i=1;i<cnt;i++){
		for(int j=t;j>=w[i];j--){
				dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
		}
	}

< https://www.luogu.com.cn/problem/P1776 > 宝物筛选

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值