多重背包问题

本文详细探讨了多重背包问题的动态规划解决方案,包括朴素做法、二进制优化、空间优化以及利用单调队列进行的时间复杂度优化。通过对物品数量的二进制拆分和使用单调队列,将时间复杂度降低到O(n×v),同时解决了空间复杂度问题,实现了算法的高效运行。

多重背包问题

状态表示: f[i][j]f[i][j]f[i][j]: 在前 iii 个数中选, 每个物品不超过 sis_isi 个体积总和不超过 jjj 的集合
状态属性: 总价值的最大值
状态计算
1.第 iii个物品选 000
f[i][j]=max(f[i][j],f[i−1][j−0∗v[i]]+0∗w[i]);f[i][j]= max(f[i][j], f[i - 1][j - 0 * v[i]] + 0 * w[i]);f[i][j]=max(f[i][j],f[i1][j0v[i]]+0w[i]);
2.第 iii 个物品选 111
f[i][j]=max(f[i][j],f[i−1][j−1∗v[i]]+1∗w[i])f[i][j] = max(f[i][j], f[i - 1][j - 1 * v[i]] + 1 * w[i])f[i][j]=max(f[i][j],f[i1][j1v[i]]+1w[i]);
3.第 iii 个物品选 222
f[i][j]=max(f[i][j],f[i−1][j−3∗v[i]]+2∗w[i]);f[i][j] = max(f[i][j], f[i - 1][j - 3 * v[i]] + 2 * w[i]);f[i][j]=max(f[i][j],f[i1][j3v[i]]+2w[i]);
4.第 iii 个物品选 333
f[i][j]=max(f[i][j],f[i−1][j−4∗v[i]]+4∗w[i]);f[i][j] = max(f[i][j], f[i - 1][j - 4 * v[i]] + 4 * w[i]);f[i][j]=max(f[i][j],f[i1][j4v[i]]+4w[i]);
.........

第i个物品选 sssf[i][j]=max(f[i][j],f[i−1][j−s[i]∗v[i]]+s[i]∗w[i]);f[i][j] = max(f[i][j], f[i - 1][j - s[i] * v[i]] + s[i] * w[i]);f[i][j]=max(f[i][j],f[i1][js[i]v[i]]+s[i]w[i]);

1.朴素做法 时间复杂度 O(n × m × s)

for(int i = 1; i <= n; i ++ )
    for(int j = 0; j <= m; j ++ )
    for(int k = 0; k <= s[i] && j >= k * v[i]; k ++ )
        f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);

2.一些奇奇怪怪的做法 时间复杂度: 比朴素做法还慢
sis_isi 个物品拆分成一个个物品 当做 010101 背包来做

int cnt = n;
    for(int i = 1; i <= n; i ++ )
    {
        cin >> v[i] >> w[i] >> s[i];
        for(int j = 1; j < s[i]; j ++ )
        v[ ++ cnt] = v[i], w[cnt] = w[i];
    }   
    for(int i = 1; i <= cnt; i ++ )
    for(int j = 0; j <= m; j ++ )
    {
    f[i][j] = f[i - 1][j];
    if(j >= v[i])
        f[i][j] = max(f[i][j], f[i - 1][j - v[i]] +  w[i]);
    }

3.二进制优化 时间复杂度 O(n2logsn^2logsn2logs)
如果直接遍历转化为 010101 背包问题,是每次都拿一个来问,取了好还是不取好。那么根据数据范围,这样的时间复杂度是O(n3n^3n3),这个时间复杂度很不理想,我们肯定想去优化它,我们不难知道任何一个整数都能用一个二进制数来表示,所以我们可以将s个物品拆分成多个二进制数, 如9=20+239 = 2^0 + 2^39=20+23, 999 个物品可以拆分为体积为 8v8v8v,价值为 8w8w8w 的物品和体积为 vvv,价值为 www 的物品,这样可以大大降低时间复杂度

for(int i = 1; i <= n; i ++ )
    {
        int V, W, S;
        cin >> V >> W >> S;
        int k = 1;
        while(k <= S)
        {
            v[++ cnt] = k * V;
            w[cnt] = k * W;
            S -= k;
            k  <<= 1;
        }
        if(S > 0 ) v[++ cnt] = S * V, w[cnt] = S * W;
    }
    n = cnt;
    for(int i = 1; i <= n; i ++ )
    for(int j = 0; j <= m; j ++ )
    {
            f[i][j] = f[i - 1][j];
            if(j >= v[i])
            f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
    }

代码看起来很合理,时间复杂度也很满意,但是当你提交时会发现SE了, 为啥? 答案是f数组开两维需要的空间很大,容易爆内存,所以我们需要进行空间优化

3.空间优化
通过完全背包和 010101 背包的空间优化,我们肯定也想对多重背包进行空间优化
010101背包 f[i][j]=max(f[i][j],f[i−1][j−v[i]]+w[i]);f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);f[i][j]=max(f[i][j],f[i1][jv[i]]+w[i]);
完全背包 f[i][j]=max(f[i][j],f[i−1][j−v[i]]+w[i]);f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);f[i][j]=max(f[i][j],f[i1][jv[i]]+w[i]);
我们会发现惊人的相似,不,就是完全一样,那我们自然可以想到从大到小枚举体积,然后将空间压缩为一维

for(int i = 1; i <= n; i ++ )
    {
        int V, W, S;
        cin >> V >> W >> S;
        int k = 1;
        while(k <= S)
        {
            v[++ cnt] = k * V;
            w[cnt] = k * W;
            S -= k;
            k  <<= 1;
        }
        if(S > 0 ) v[++ cnt] = S * V, w[cnt] = S * W;
    }
    n = cnt;
    for(int i = 1; i <= n; i ++ )
    for(int j = m; j >= v[i]; j -- )
    f[j] = max(f[j], f[j - v[i]] + w[i]);

但是我们这时候会想,为啥我们不能像完全背包哪一样优化呢,我们可以试着模仿完全背包做一下
在这里插入图片描述

我们会发现下面比上面多了一项,但是完全背包为啥项数是相同的呢? 因为完全背包是可以取无穷次的,所以项数一定是相同的,因为多重背包上下项数不同,所以我们不能进行优化

4.进行了上述的空间时间优化后,我们还是不够满意 我们尝试能不能将时间复杂度降低到O(N×VN × VN×V) 呢? 答案是可以的,那要怎么优化呢? 我们将会用到一个数据结构, 叫单调队列

f[i][j]=max(f[i−1][j],f[i−1][j−v]+w,⋯,f[i−1][j−s∗v]+s∗w);f[i][j] = max(f[i − 1][j],f[i − 1][j − v] + w,⋯,f[i − 1][j − s * v] + s * w);f[i][j]=max(f[i1][j],f[i1][jv]+w,,f[i1][jsv]+sw);
f[i][j−v]=max(f[i−1][j−v],...f[i−1][j−(s+1)∗v]+s∗w);f[i][j − v] = max( f[i − 1][j − v], ... f[i − 1][j − (s + 1) * v] + s * w);f[i][jv]=max(f[i1][jv],...f[i1][j(s+1)v]+sw);
f[i][j−2∗v]=max(f[i−1][j−2∗v],f[i−1][j−3∗v]+w,⋯,f[i−1][j−(s+2)∗v)+s∗w]f[i][j− 2 * v] = max(f[i − 1][j − 2 * v], f[i − 1][j − 3 * v] + w,⋯,f[i − 1][j − (s + 2) * v) + s * w]f[i][j2v]=max(f[i1][j2v],f[i1][j3v]+w,,f[i1][j(s+2)v)+sw]
.........
f[i][r+s∗v]=max(f[i−1][r+s∗v],f[i−1][r+(s−1)∗v)]+w,...,f[i−1][r])+sw);f[i][r + s * v]= max(f[i − 1][r + s * v],f[i − 1][r + (s − 1) * v)] + w,...,f[i − 1][r])+sw);f[i][r+sv]=max(f[i1][r+sv],f[i1][r+(s1)v)]+w,...,f[i1][r])+sw);
f[i][r+(s−1)∗v]=max(f[i−1][r+(s−1)∗v],...,f[i−1][r]+(s−1)∗w)f[i][r + (s − 1) * v] = max(f[i−1][r + (s − 1) * v],...,f[i − 1][r]+ (s − 1) * w)f[i][r+(s1)v]=max(f[i1][r+(s1)v],...,f[i1][r]+(s1)w)
f[i][r+2∗v]=max(f[i−1][r+2∗v],f[i−1][r+v]+w,f[i−1][r])+2∗w)f[i][r + 2 * v] = max(f[i − 1][r + 2 * v],f[i − 1][r + v] + w, f[i − 1][r]) + 2 * w)f[i][r+2v]=max(f[i1][r+2v],f[i1][r+v]+w,f[i1][r])+2w)
f[i][r+v]=max(f[i−1][r+v],f[i−1][r]+w)f[i][r + v] = max(f[i − 1][r + v],f[i − 1][r] + w)f[i][r+v]=max(f[i1][r+v],f[i1][r]+w)
f[i][r]=f[i−1][r]f[i][r] = f[i − 1][r]f[i][r]=f[i1][r]

为了更好的阅读体验我们可以先把 iii 去掉
f[j]=max(f[j],f[j−v]+w,⋯,f[j−s∗v]+s∗w);f[j] = max(f[j],f[j − v] + w,⋯,f[j − s * v] + s * w);f[j]=max(f[j],f[jv]+w,,f[jsv]+sw);
f[j−v]=max(f[j−v],...f[j−(s+1)∗v]+s∗w);f[j − v] = max( f[j − v], ... f[j − (s + 1) * v] + s * w);f[jv]=max(f[jv],...f[j(s+1)v]+sw);
f[j−2∗v]=max(f[j−2∗v],f[j−3∗v]+w,⋯,f[j−(s+2)∗v)+s∗w]f[j− 2 * v] = max(f[j − 2 * v], f[j − 3 * v] + w,⋯,f[j − (s + 2) * v) + s * w]f[j2v]=max(f[j2v],f[j3v]+w,,f[j(s+2)v)+sw]
.........
f[r+s∗v]=max(f[r+s∗v],f[r+(s−1)∗v)]+w,...,f[r])+sw);f[r + s * v]= max(f[r + s * v],f[r + (s − 1) * v)] + w,...,f[r])+sw);f[r+sv]=max(f[r+sv],f[r+(s1)v)]+w,...,f[r])+sw);
f[r+(s−1)∗v]=max(f[r+(s−1)∗v],...,f[r]+(s−1)∗w)f[r + (s − 1) * v] = max(f[r + (s − 1) * v],...,f[r]+ (s − 1) * w)f[r+(s1)v]=max(f[r+(s1)v],...,f[r]+(s1)w)
f[r+2∗v]=max(f[r+2∗v],f[r+v]+w,f[r])+2∗w)f[r + 2 * v] = max(f[r + 2 * v],f[r + v] + w, f[r]) + 2 * w)f[r+2v]=max(f[r+2v],f[r+v]+w,f[r])+2w)
f[r+v]=max(f[r+v],f[r]+w)f[r + v] = max(f[r + v],f[r] + w)f[r+v]=max(f[r+v],f[r]+w)
f[r]=f[r]f[r] = f[r]f[r]=f[r]
看到这里你可能会疑惑了, 这是啥啊, 我咋看不懂, r是啥,为啥前面是减去v,后面咋又加上 vvv
在这里, rrr 是指的 jjj 除以 vvv 后的余数,因为用一个物品会消耗v,用s个物品会消耗 s×vs × vs×v 的体积, 最后会剩余一部分体积,即 jj % vj, 所以 f[i][j−v]f[i][j - v]f[i][jv] 最后就会变成 f[i][r]f[i][r]f[i][r]
然后往回倒退,每次就是加上 vvv

然后再把这个递推式倒过来观察
f[i][r]=f[i−1][r]f[i][r] = f[i − 1][r]f[i][r]=f[i1][r]
f[i][r+v]=max(f[i−1][r+v],f[i−1][r]+w)f[i][r + v] = max(f[i − 1][r + v],f[i − 1][r] + w)f[i][r+v]=max(f[i1][r+v],f[i1][r]+w)
f[i][r+2∗v]=max(f[i−1][r+2∗v],f[i−1][r+v]+w,f[i−1][r])+2∗w)f[i][r + 2 * v] = max(f[i − 1][r + 2 * v],f[i − 1][r + v] + w, f[i − 1][r]) + 2 * w)f[i][r+2v]=max(f[i1][r+2v],f[i1][r+v]+w,f[i1][r])+2w)
.........

f[i][r+(s−1)∗v]=max(f[i−1][r+(s−1)∗v],...,f[i−1][r]+(s−1)∗w)f[i][r + (s − 1) * v] = max(f[i − 1][r + (s − 1) * v],...,f[i − 1][r] + (s − 1) * w)f[i][r+(s1)v]=max(f[i1][r+(s1)v],...,f[i1][r]+(s1)w)
f[i][r+s∗v]=max(f[i−1][r+s∗v],f[i−1][r+(s−1)∗v)]+w,...,f[i−1][r])+s∗w);f[i][r + s * v] = max( f[i − 1][r + s * v],f[i − 1][r + (s − 1) * v)] + w,...,f[i − 1][r]) + s * w);f[i][r+sv]=max(f[i1][r+sv],f[i1][r+(s1)v)]+w,...,f[i1][r])+sw); 滑动窗口已满
f[i][r+(s+1)∗v]=max(f[i−1][r+(s+1)∗v],f[i−1][r+s∗v]+w,⋯,f[i−1][r+v]+s∗w);f[i][r + (s + 1) * v] = max(f[i - 1][r + (s + 1) * v], f[i - 1][r + s * v] + w,⋯,f[i - 1][r + v]+ s * w);f[i][r+(s+1)v]=max(f[i1][r+(s+1)v],f[i1][r+sv]+w,,f[i1][r+v]+sw); 滑动窗口已满

.........

f[i][j−2∗v]=max(f[i−1][j−2∗v],f[i−1][j−3∗v]+w,⋯,f[i−1][j−(s+2)∗v)+s∗w];f[i][j− 2 * v] = max(f[i − 1][j − 2 * v], f[i − 1][j − 3 * v] + w,⋯,f[i − 1][j − (s + 2) * v) + s * w];f[i][j2v]=max(f[i1][j2v],f[i1][j3v]+w,,f[i1][j(s+2)v)+sw]; 滑动窗口已满
f[i][j−v]=max(f[i−1][j−v],...f[i−1][j−(s+1)∗v]+s∗w);f[i][j − v] = max(f[i − 1][j − v], ... f[i − 1][j − (s + 1) * v] + s * w);f[i][jv]=max(f[i1][jv],...f[i1][j(s+1)v]+sw); 滑动窗口已满
f[i][j]=max(f[i−1][j],f[i−1][j−v]+w,⋯,f[i−1][j−s∗v]+s∗w);f[i][j] = max(f[i − 1][j],f[i − 1][j − v] + w,⋯,f[i − 1][j − s * v] + s * w);f[i][j]=max(f[i1][j],f[i1][jv]+w,,f[i1][jsv]+sw); 滑动窗口已满

或许你还没有看懂,那么你可以看这里

frf_rfr
fr+2v,fr+v,frf_{r + 2v},f_{r + v}, f_rfr+2v,fr+v,fr
fr+(s−1)v,fr+(s−2)v,...fr+2v,fr+v,frf_{r + (s - 1)v}, f_{r + (s - 2)v}, ... f_{r + 2v}, f_{r + v}, f_rfr+(s1)v,fr+(s2)v,...fr+2v,fr+v,fr
fr+sv,fr+(s−1)v,...fr+2v,fr+v,frf_{r + sv}, f_{r + (s - 1)v}, ... f_{r + 2v}, f_{r + v}, f_rfr+sv,fr+(s1)v,...fr+2v,fr+v,fr

.........

fj−2v,fj−3v,fj−4v,...fj−(s+2)vf_{j - 2v}, f_{j - 3v}, f_{j - 4v}, ... f_{j - (s + 2)v}fj2v,fj3v,fj4v,...fj(s+2)v
fj−v,fj−2v,fj−3v,...fj−(s+1)vf_{j - v}, f_{j - 2v}, f_{j - 3v}, ... f_{j - (s + 1)v}fjv,fj2v,fj3v,...fj(s+1)v
fj,fj−v,fj−2v,...fj−svf_j, f_{j - v}, f_{j - 2v},... f_{j - sv}fj,fjv,fj2v,...fjsv

这里差不多就能看出规律了,然后我们再把 www 加上去,

frf_rfr
fr+2v,fr+v+w,fr+2wf_{r + 2v}, f_{r + v} + w, f_r + 2wfr+2v,fr+v+w,fr+2w
fr+(s−1)v,fr+(s−2)v+w,...fr+2v+(s−3)w,fr+v+(s−2)w,fr+(s−1)wf_{r + (s - 1)v}, f_{r + (s - 2)v} + w, ... f_{r + 2v} + (s - 3)w, f_{r + v} + (s - 2)w, f_r + (s - 1)wfr+(s1)v,fr+(s2)v+w,...fr+2v+(s3)w,fr+v+(s2)w,fr+(s1)w
fr+sv,fr+(s−1)v+w,...fr+2v+(s−2)w,fr+v+(s−1)w,fr+swf_{r + sv}, f_{r + (s - 1)v} + w, ... f_{r + 2v} + (s - 2)w, f_{r + v} + (s - 1)w, f_r + swfr+sv,fr+(s1)v+w,...fr+2v+(s2)w,fr+v+(s1)w,fr+sw

然后我们要将 v,wv,wv,w 建立起关系,我们提取 www

frf_rfr
fr+2v−2w,fr+v−w,frf_{r + 2v} - 2w, f_{r + v} -w, f_rfr+2v2w,fr+vw,fr
fr+(s−1)v−(s−1)w,fr+(s−2)v+(s−2)w,...fr+2v−2w,fr+v−w,frf_{r + (s - 1)v} - (s - 1)w, f_{r + (s - 2)v} + (s - 2)w, ... f_{r + 2v} - 2w, f_{r + v} - w, f_rfr+(s1)v(s1)w,fr+(s2)v+(s2)w,...fr+2v2w,fr+vw,fr
fr+sv−sw,fr+(s−1)v−(s−1)w,...fr+2v−2w,fr+v−w,frf_{r + sv} - sw, f_{r + (s - 1)v} - (s - 1)w, ... f_{r + 2v} - 2w, f_{r + v} - w, f_rfr+svsw,fr+(s1)v(s1)w,...fr+2v2w,fr+vw,fr

可以发现每个序列的长度不超过 s+1s+1s+1,因为最多可以选 sss个,然后不选也是一种情况,共 s+1s + 1s+1 种情况
于是通过该 滑动窗口 ,我们就能在 线性 的时间里求出 i 阶段里,所有满足 j=rj = r % vj=rf[i][j]f[i][j]f[i][j] 滑动窗口
求 最大值 的实现,只需利用 队列 在队头维护一个 最大值 的 单调递减 的 单调队列 即可
为了更新所有 iii 阶段里的状态 f[i][j]f[i][j]f[i][j],我们只需再额外枚举所有的 余数 r 即可
时间复杂度:O(n×vn × vn×v )
空间复杂度:O( n×vn × vn×v)
滑动窗口的长度为 s+1s+1s+1

 for(int i = 0; i < n; i ++ )
     {
        memcpy(pre,f,sizeof f);   //把上一层计算的状态复制过来
        int v,s,w;  //体积 次数 价值
        v = read(), w = read(), s = read();
        for(int j = 0; j < v; j ++ )  //枚举每一个余数 0~v-1
        //枚举 %v 的余数 因为例如 当v = 3 ,f[0],f[1],f[2]是不同的集合 
        //当s = 3  f[12] 可以由 f[9],f[6],f[3] 转化而来 所以%v 余数相同的 f[j] 可以划分在同一个集合 
        {
           int hh = 0, tt = -1;
           for(int k = j; k <= m; k += v)    
           //因为j是 %v的余数  所以枚举 还原j, %v 就相当于减去了n 个 v  我们在不超过容积m的前提下,持续递增k
           {
             //利用单调队列
            //1.超出滑动窗口的 弹出队列
            if(hh <= tt && q[hh] < k - s * v)  ++hh;
                //当队列不空  当 滑动窗口的队头 离开了窗口,则将队头出队,
                //因为s为最大数量,所以滑动窗口的最大长度为s 从当前位置k开始 往前推s个位置 
                //如果队头处于k-s*v之前 则队头离开了滑动窗口,将其出队
            while(hh <= tt && pre[q[tt]] - (q[tt] - j) / v * w <= pre[k] - (k - j) / v * w ) tt--;
              //当队列不空  为保持滑动窗口的单调递减   滑动窗口的尾值必须大于新入队的元素 否则将队尾出队
              //队尾元素是 pre[j + k * v] - k * w,  q[tt] == j + k * v  - > (q[tt] - j )/v == k 
              if(hh <= tt )     f[k] = max(f[k],pre[q[hh]] + (k - q[hh]) / v * w);
                //更新时 用 队头元素 + 中间间隔了多少个w  即k*w 来更新
                //当队列不空时更新f   f数组储存的是滑动窗口的最大值  
                //将新元素入队  
              q[++tt] = k;
           } 
        }
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

广西小蒟蒻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值