• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

RomanLin

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

【多重背包 二进制优化版】洛谷 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\) 项和公式,有:

\[\begin{aligned} S_n &= \frac{a_1 \cdot (1 - q^n)}{1-q} \\ &= \frac{1 \cdot (1 - 2^n)}{1 - 2} \\ &= \frac{1 - 2^n}{-1} \\ &= 2^n - 1 \end{aligned} \]

因此最后一项就是:\(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;
}

posted on 2026-09-08 21:34  RomanLin  阅读(9)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3