多重背包问题(二进制优化)

我们先回顾一下多重背包的代码
最暴力的解法:

for(int i=1;i<=N;i++){
//第i个物品
	for(int j=0;j<=V;j++){
	//当前背包空间为j
		for(int k=0;k<=n[i];k++){
		//k个第i种物品
		//k个第i种物品占k*c[i],所剩背包容积为j-k*c[i],价值k*w[i]
		//不用第i种物品,只用前(i-1)种物品,所剩背包容积j-k*c[i]为dp[i-1][j-c[i]*k]
			dp[i][j]=max(dp[i-1][j-c[i]*k]+w[i]*k,dp[i][j]);
		}
	}
}

对于第i个物品,他所用到的结果应该是
i从小到大,所以当你算dp[j]时,你所用到的只有dp[j],挨个更新是完全没必要的,所以就应该从大到小计算。

(空间优化版)

for(int i=1;i<=N;i++){
	//第i个物品
	for(int j=V;j>=0;j--){
	//
		for(int k=1;k<=n[i];k++){
		//选k个第i个物品
			if(j>=c[i]*k){
				dp[j]=max(dp[j-c[i]*k]+w[i]*k,dp[j]);
			}
		}
	}
}

在空间优化这版中,我们要把所有可能性都尝试一遍,例如有a类物品3个,b类物品4个,c类物品5个,那么我们就需要把a类物品从0~3尝试,b类物品从0 ~ 4尝试,c类物品从0 ~ 5 尝试。
我们曾经的想法是看n[1]~n[N]之和的每个物品全部分开,你可能会觉得这个比空间优化版复杂了好多,但其实是和空间优化版一模一样的时间复杂度。

现在开始进入正题:
首先,先来思考一个问题
我们用有限的几个数,使这几个数字可以凑出1~x尽可能多的和值,如果想使x尽可能大,那么应该怎么拼呢?
首先有了1、2、3你就能凑出1~6。
有个1、2、4,你就能凑出1~7。
下一个再来一个3,行吗?它能拼到10了。
假如说我想凑到30呢?
用1,2,4,8……2k 能凑到2k+1-1
借助1,2,4,8能把1~15的所有数都凑出来
我们要考虑的都是每个数选还是不选,似曾相识吗?这不就是01背包
这是一个很常见的思想——二进制优化
对于任意的一个数,我们应该怎么算呢?
比如说能且仅能凑出1~28之间的所有数,我们应该拆成哪几个数呢?
1+2+4+8可以拼到1~15之间所有的数
16要不要加?其实是不能加的,如果加了就可以凑出1 ~ 31之间的所有数了,而要求是1~28之间的所有数,正常是不能超过28的。
应该加的数是13,因为前4个数的和是2k-1,想计算最后一个数的方法就是N-(2k-1),(k是i-1)。

现在只需要改成01背包的思路,第i个物品选k个,k的取值范围应该是1~n[i],只要找到几个数能凑出1 ~ n[i]即可

总结一下计算的过程:
1+2+4+8+……+16(直到没有超出规定范围),最后一个数就是n[i]-(2k-1)

现在再思考一下n[i]为40,应该怎么算,如果想让k凑出1~n[i],需要的数字就是1+2+4+8+16+9。现在就可以把枚举k的循环删掉了。

代码是

#include <iostream>
using namespace std;
int n[110], c[110], w[110];
int nc[1000], nw[1000];
int dp[5010];
int main() {
    int N,V;
    cin >>N>>V;
    for (int i=1;i<=N;++i) {
        cin>>w[i]>>c[i]>>n[i];
    }
	for(int i=1;i<=N;i++){
		//枚举每一种物品
		int k;
		//k是枚举2的多少次方的这个数,<<符号表示二进制运算(位运算),1<<k表示2^k^
		for(k=1;n[i]-(1<<k)+1>0;k++){
		//新的物品的体积和价值
			nc[ncnt]=(1<<(k-1))*c[i];
			nw[ncnt]=(1<<(k-1))*w[i];
			ncnt++;
		}
		//此时超出范围了1个,就应该再退回去
		k--;
		//最后一组
		nc[ncnt]=(n[i]-(1<<k)+1)*c[i];
		nw[ncnt]=(n[i]-(1<<k)+1)*w[i];
		ncnt++;
	}
	for(int i=0;i<ncnt;i++){
		for(int j=V;j>=nc[i];j--){
			dp[j]=max(dp[j],dp[j-nc[i]]+nw[i]);
		}
	}
	cout<<dp[V];
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值