多重背包问题的三种复杂度解法,O(n * w * c)、O(n*w*log c)和O(n * w)。

本文介绍了多重背包问题的三种解法:O(n*w*c)、O(n*w*logc)及O(n*w)。通过详细解释每种算法的思想,并提供代码实现,帮助读者深入理解并掌握这些高效解题技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

吹水:

初一的时候就遇到了要求快速解决多重背包问题的题目,当时没有总结的习惯,结果最近遇到的时候还有些懵,感觉基础不是很牢固,需要巩固一下,在这里写一下自己对题目中的两种做法的理解。

O(n * w *c)解法:

相信不用解释这个解法大家都懂,之所以列出来是为了作为后面的参考。

#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
using namespace std;

const int Maxn = 1005, Maxm = 1005;
int n, m, w[Maxn], v[Maxn], c[Maxn], f[Maxm + 1];

int main() {
    freopen("a.in", "r", stdin);
    scanf("%d", &n);
    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);
    fo(i, 1, n) {
        fd(j, Maxm, w[i])
            fo(k, 1, min(c[i], j / w[i]))
                f[j] = max(f[j], f[j - k * w[i]] + k * v[i]);
    }
    scanf("%d", &m); printf("%d", f[m]);
}

O(n*w*log c)解法:

先解释一下:n是物品个数,w是weight,即物体所占的份额,c就是物体可以有多少个。
对于当前的物品,若它能使用的个数为c,k是最大的正整数且满足2k+1<=ck=logc2-1),y = c(2k+11)
我们可以把原来可以用c次的物品拆分成k+2个只能用一次的物品。
前k+1个物品分别是:wi=w2i,vi=c2i,ci=1(0<=i<=k)
第k+2个物品是:w=wy,v=vy,c=1
转换成了01背包问题。
这样子,我们充分利用了二进制的原理,可以刚好表示出用了次数为0-c的原物品。

#include<cstdio>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int Maxn = 1005, Maxm = 1005;
int n, m, a2[35], w[Maxn], v[Maxn], c[Maxn], f[Maxm + 1];

int main() {
    freopen("a.in", "r", stdin);
    a2[0] = 1; fo(i, 1, 30) a2[i] = a2[i - 1] * 2;
    scanf("%d", &n);
    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);
    fo(i, 1, n) {
        int k = log2(c[i]);
        fo(z, 0, k)
            fd(j, Maxm, a2[z] * w[i])
                f[j] = max(f[j], f[j - a2[z] * w[i]] + a2[z] * v[i]);
        int y = c[i] - a2[k];
        fd(j, Maxm, y * w[i])
            f[j] = max(f[j], f[j - y * w[i]] + y * v[i]);
    }
    scanf("%d", &m); printf("%d", f[m]);
}

O(n * w)做法:

这已经是一个线性做法了,用了单调队列。
让我们观察一下暴力的dp式(为了方便,下面的除号全部默认下取整):
fi=max(fijw+jv)(1<=j<=min(c,j/w))
等价于fi=max(fk+(ik)/wv)(k=i(mod w)) and ((ik)/w<=c)
于是我们可以想到以(i % w)为关键字开w条单调队列辅助dp。
对于每一条队列,最方便的是存已占的份额(w)值,若队列中有两个x,y,且(x <y),则必须满足:
fx+(yx)/wv>=fy
且队列头start对于当前的i必须满足(istart)/w<=c
也许我们还需要一个滚动数组。

Code:

#include<cstdio>
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define max(a, b) ((a) > (b) ? (a) : (b))
using namespace std;

const int Maxn = 1000005, Maxm = 1000005;

int n, m, o, w[Maxn], v[Maxn], c[Maxn], f[2][Maxm + 1];
int d[Maxn];

int main() {
    scanf("%d", &n);
    fo(i, 1, n) scanf("%d %d %d", &w[i], &v[i], &c[i]);
    fo(i, 1, n) {
        o = !o;
        fo(mo, 0, w[i] - 1) {
            int st = 1, en = 0;
            fo(j, 0, max(0, (Maxm - mo) / w[i])) {
                int k = mo + j * w[i];
                while(st <= en && (k - d[st]) / w[i] > c[i]) st ++;
                while(st <= en && f[!o][d[en]] + (k - d[en]) / w[i] * v[i] < f[!o][k]) en --;
                d[++ en] = k;
                if(st <= en) f[o][k] = f[!o][d[st]] + (k - d[st]) / w[i] * v[i]; else f[o][k] = 0;
            }
        }
    }
    scanf("%d", &m); printf("%d\n", f[o][m]);
}

总结:

其实log算法在小数据中比队列算法还要快,因为常数小,所以比赛时看数据而定吧。

<think>嗯,用户问的是如何将多重背包问题转化为01背包,并用二进制思想降低复杂度。这个问题我之前学习过,但需要仔细回忆一下细节,确保解释清楚。 首先,用户可能已经了解过01背包完全背包问题,现在遇到了多重背包的情况,也就是每个物品有数量限制。多重背包的基本解法可能已经不能满足效率要求,所以他们想知道如何优化,特别是用二进制优化的方法。 我需要先回顾多重背包的基本概念。多重背包每个物品有s个可用,直接的做法是把每个物品拆成s个单独的物品,然后当作01背包处理。但这样时间复杂度是O(V*Σs),当s很大时,比如几万甚至更多,这样的复杂度会很高,无法处理。 接下来是二进制优化的思路。二进制优化的核心在于如何用更少的物品数来表示原来的s个物品,从而减少总的物品数量。例如,数字7可以用1、2、4三个数组合,覆盖1到7的所有可能取值。这样,原本拆成7个物品,现在只需要拆成3个,大大减少了数量。 那如何具体拆分呢?比如一个物品有s个,可以拆分成2^0, 2^1, ..., 2^k,直到剩下的部分不足2^(k+1),然后将剩下的作为最后一个数。比如s=10,拆成1,2,4,3。这样总共有4个新物品,每个的数量是这些二进制数,对应的体积价值也是原来的乘以这些数。这样拆分后,每个新物品只能选一次,就转化为01背包问题了。 需要注意的是,这样的拆分方法是否真的能覆盖所有可能的选取数量。比如,用1,2,4可以组合出0-7,再加上3的话,就能组合出0-10的所有可能。所以拆分是正确的。 然后,用户可能需要一个具体的例子来说明这个过程。比如物品有体积v,价值w,数量s=7。拆分成1,2,4这三个数,对应的体积价值分别是1*v, 2*v,4*v1*w,2*w,4*w。这样,选这三个物品的组合可以表示选取1到7个原物品的情况。例如,选1+2=3个,或者4个,或者1+4=5个等等。 这样做的时间复杂度如何呢?原本拆成s个,现在每个物品拆成log2(s)个,所以总物品数从Σs变为Σlog s_i,这样在处理大数据量时,复杂度大大降低。 另外,用户可能想知道这种方法为什么有效,或者有没有其他优化方法,比如单调队列优化。但这里用户特别提到二进制优化,所以重点应该放在这上面。 最后,总结步骤:拆分每个物品的数量为二进制数,生成对应的新物品,转化为01背包问题,然后使用01背包解法。这样可以有效降低时间复杂度,适用于数量较大的情况。 可能需要检查是否有理解错误的地方,比如拆分后的组合是否正确,或者是否每个物品的处理都正确。同时,是否需要代码示例或者数学证明来加强解释? 总之,回答的结构应该是先解释多重背包的问题,然后介绍直接拆分的缺点,再引入二进制优化的思路,详细说明如何拆分,举例说明,并讨论复杂度优化后的效果。最后可以给出代码的大致思路或步骤,帮助用户更好地理解。</think>### 多重背包转01背包 + 二进制优化详解 --- #### **一、问题背景** **多重背包问题**: - 每个物品有**数量上限** $s_i$(不同于01背包的1个、完全背包的无限个) - 目标:在容量 $C$ 的背包中选择物品,使总价值最大 - **直接解法**:将每个物品拆成 $s_i$ 个独立物品 → 转化为01背包复杂度 $O(C \cdot \sum s_i)$(当 $s_i$ 很大时效率极低) --- #### **二、二进制优化核心思想** **核心洞察**:用**二进制组合**代替逐个拆分,将 $s_i$ 的线性拆分优化为对数级拆分 **关键步骤**: 1. 将数量 $s_i$ 分解为 $2^0, 2^1, ..., 2^k, r$(其中 $r = s_i - (2^{k+1} - 1)$) 2. 对每个二进制分量创建**虚拟物品**(体积=分量数$\times$原体积,价值=分量数$\times$原价值) 3. 转化为01背包问题求解 **数学验证**: 任意整数 $n$ 可表示为 $n = 2^0 + 2^1 + ... + 2^k + r$,其中 $r < 2^{k+1}$ 例如:$10 = 1 + 2 + 4 + 3$,覆盖所有 $0 \leq x \leq 10$ 的组合可能 --- #### **三、具体实现步骤** **示例**:物品A(体积 $v=2$,价值 $w=3$,数量 $s=7$) **二进制拆分**: - 分解 $7 = 1 + 2 + 4$ - 创建3个虚拟物品: - (体积 $1\times2=2$,价值 $1\times3=3$) - (体积 $2\times2=4$,价值 $2\times3=6$) - (体积 $4\times2=8$,价值 $4\times3=12$) **组合能力**: - 选0个:不选任何虚拟物品 - 选1-7个:通过组合虚拟物品实现 (例如:选1+2=3个 → 选前两个虚拟物品) --- #### **四、复杂度对比** | 方法 | 拆分后物品总数 | 时间复杂度 | |------------|---------------------|----------------------| | 直接拆分 | $\sum s_i$ | $O(C \cdot \sum s_i)$| | 二进制优化 | $\sum \lfloor \log_2 s_i \rfloor +1$ | $O(C \cdot \sum \log s_i)$ | **优化效果**:当 $s_i=2000$ 时,拆分次数从2000次 → 仅需11次(因为 $2^{11}=2048$) --- #### **五、代码实现框架** ```cpp vector<Item> binary_optimize(vector<int>& v, vector<int>& w, vector<int>& s) { vector<Item> new_items; for (int i = 0; i < v.size(); ++i) { int cnt = s[i]; for (int k = 1; k <= cnt; k *= 2) { // 二进制拆分 new_items.push_back({k * v[i], k * w[i]}); cnt -= k; } if (cnt > 0) // 处理剩余数量 new_items.push_back({cnt * v[i], cnt * w[i]}); } return new_items; // 后续用01背包求解 } ``` --- #### **六、实际应用示例** **输入**: - 背包容量 $C=10$ - 物品列表:[(v=3, w=2, s=5), (v=5, w=4, s=3)] **二进制拆分结果**: 1. 第一个物品拆分:1,2,2(因为5=1+2+2) - 虚拟物品:(3,2), (6,4), (6,4) 2. 第二个物品拆分:1,2(因为3=1+2) - 虚拟物品:(5,4), (10,8) **转化后的01背包问题**: - 总物品数:3 + 2 = 5(原问题拆分后为5+3=8,优化减少3次计算) --- #### **七、常见疑问解答** **Q1:为什么不能直接按2的幂拆分?** 答:当剩余数量 $r$ 不是2的幂时,需单独处理剩余部分(例如拆分10时的最后一项3) **Q2:如何保证组合覆盖所有可能数量?** 答:二进制拆分本质是**二进制的位权覆盖原理**,数学上可证明任意整数都能被表示为不同2的幂之(或加剩余项) --- #### **八、延伸思考** - **单调队列优化**:时间复杂度可进一步优化到 $O(NC)$(适用于超大容量场景) - **混合背包**:结合01背包、完全背包多重背包的组合问题处理技巧 通过二进制优化,多重背包的时间复杂度**指数级爆炸**优化到**可接受范围**,是动态规划中经典的优化思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值