【笔记】二进制拆分

二进制拆分

二进制拆分是对多重背包的一种优化方式,可以极大的优化多重背包的时间。

前置

我们回顾下完全背包问题。

背包容积为 \(C\) , 有 \(n\) 种物品 , 每种物品有 \(k[i]\) 个, 第 \(i\) 个物品占用 \(w[i]\) 的容积,价值为 \(v[i]\) 。问能用背包装进的物品和的最大值。

常规做法是将其转化成01背包来做,将 \(1\) ~ \(k[i]\) 个第 \(i\) 类物品加入到01背包中。

时间复杂度为:\(O(C \times \sum_{i=1}^{n} k[i] )\)

二进制拆分依旧是将完全背包转化为01背包,不过是将 \(k[i]\) 个物品变成 \(log(k[i])\) 个物品加入到01背包中。

时间复杂度为:\(O(C \times \sum_{i=1}^{n} log (k[i]) )\)

原理

一个数可以被拆分为任意二进制的和。

例如:$7= 2^0 + 2^1 +2^2 $

任意一个数都可以表示为几个 \(2\) 的多少次方之和的形式。

正常将完全背包转移成01背包时,我们从小到大枚举放j个第 \(i\) 类物品会更优,但这样其实是会有重复枚举的。

为什么呢?

例如: 我们先将 \(1\) 个物品 \(i\) 放入背包,再将 \(2\) 个物品 \(i\) 放入背包后,实际上就已经表示出只放 \(3\) 个物品的情况了。

我们在背包的时候考虑将 \(x\) 个第 \(i\) 物品放入背包的情况,等同于将 \(x = 2^0 + 2^1 +....\) 个物品放入背包的情况。

例如:我们将 \(7\) 个第 \(i\) 类物品放入背包,等同于: 将 \(2^0\) 个物品放入背包,再将 \(2^1\) 个物品放入背包,再将 \(2^2\) 个物品放入背包。

这样我们就可以通过将 \(k[i]\) 个物品拆成 \(log(k[i])\) 个物品,从而表示出向背包中放入 \([1,k[i]\) 个物品的情况了。

代码实现

点击查看代码
	for (int i=1;i<=n;++i) {
		v[i]=read(); w[i]=read(); k[i]=read();
		for (int j=1;j<=k[i];j<<=1) {
		    V[++tot]=v[i]*j; W[tot]=w[i]*j;
		    k[i]-=j;
		}
		if (k[i]) {
			V[++tot]=v[i]*k[i]; W[tot]=w[i]*k[i];
		}
	}
	for (int i=1;i<=tot;++i) {
		for (int j=C;j>=V[i];--j) {
			dp[j]=max(dp[j],dp[j-V[i]]+W[i]);
		}
	}

例题:

洛谷P1782 传送门

补充:

若遇到求方案可行性的多重背包,可以用 bitset 将二进制拆分进一步优化。

利用bitset的位移实现快速转移。总时间复杂度会变成 \(O(\dfrac{C \times \sum_{i=1}^{n} log (k[i])}{\omega} )\)

posted @ 2023-11-14 18:09  int_Hello_world  阅读(255)  评论(0编辑  收藏  举报