【多重背包 二进制优化版】洛谷 P1776 宝物筛选
题目
https://www.luogu.com.cn/problem/P1776
题解
对于朴素版多重背包(参考: https://www.cnblogs.com/RomanLin/p/22864579 ),时间复杂度为:\(O(nm \sum_{i=1}^{n}{w_i})\)。对于当前题目的数据范围来说会达到 \(4e10\) 这个数量级,显然不能接受。
假设第 \(i\) 种物品有 \(w_i\) 件,是否存在一种快速计算的办法维护(时间复杂度要尽可能小于 \(O(w_i)\))出选 \(1,2,3,...,w_i\) 件的所有方案时候对答案的影响呢?答案是有的。可以将 \(w_i\) 件第 \(i\) 种物品,拆分成 \(1,2,4,8,...,w_i-2^k+1\) 件物品,只需要维护这 \(k\) 份物品,就可以维护出选 \(1,2,...,w_i\) 所有方案对答案的影响。
问题来了,最后一项的 \(w_i-2^k+1\) 是怎么来的呢?又为什么拆分成 \(1,2,4,8,...,w_i-2^k+1\) 件就可以维护出选取 \(1,2,3,...,w_i\) 件的所有方案呢?
首先不妨先不看最后一项,此时存在等比数列:$$1,2,4,8,...,2^{k-1}$$
由前 \(n\) 项和公式,有:
因此最后一项就是:\(w_i-2^k+1\)。
对于选取 \(1,2,3,...,2^k-1\) 件的任意方案,根据二进制数的性质,我们一定能通过选取 \(1, 2, 4, ..., 2^{k-1}\) 中的某几个数来凑成。
对于大于 \(2^k-1\) 件的任意方案,因为 \(w_i-2^k+1 < 2^k\),所以选取的数量 \(x\) 一定是小于 \(2^(k+1)\) 的。那么想要选取任意一种数量 \(x\) 大于 \(2^k-1\) 件的方案,只需要先选取出 \(y=min(w_i-2^k+1, x)\) 件,易知 \(x - y \leq 2^k-1\) 恒成立,那么又可以通过在前面的 \(1, 2, 4, ..., 2^{k-1}\) 件中选中某几个数,就可以凑出 \(x\)。
那么最终就可以得到代码思路:先用二进制拆分每一种的物品的数量,分别拆分为 \(1, 2, 4, ..., w_i-2^k+1\) 件物品,每一份又可以视为一个独立的物品,价值分别为 \(1 \times u_i,2 \times u_i,4 \times u_i,...,(w_i-2^k+1) \times u_i\),体积分别为 \(1 \times v_i,2 \times v_i,4 \times v_i,...,(w_i-2^k+1) \times v_i\)。然后每一份物品分别进行 0-1 背包,即可维护出最后的答案。
时间复杂度:\(O(nm \cdot logU)\)
空间复杂度:\(O(m)\)
参考代码
#include<bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);cin.tie(nullptr);
int n, m, u, v, w;
cin >> n >> m;
vector<int> dp(m + 1);
for (int i = 0; i < n; ++ i) {
cin >> u >> v >> w;// 价值 体积 数量
// 二进制拆分
for (int k = 1; w > 0; k <<= 1) {
int take = min(k, w);
w -= take;
int cost = take * v;
int val = take * u;
// 0/1背包:逆序更新
for (int j = m; j >= cost; --j) {
dp[j] = max(dp[j], dp[j - cost] + val);
}
}
}
cout << dp[m] << '\n';
return 0;
}
浙公网安备 33010602011771号