动态规划---多重背包

现在完全背包、多重背包都是和01背包进行对比

一:多重背包(1)

(一)题目要求

(二)多重背包算法实现(由于数据不大,所以我们可以使用3层循环进行处理)

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1010;

int n, m;
int f[N];    //全局变量,被初始化0

int main()
{
    cin >> n >> m;    //初始化物品数量和背包容积

    for (int i = 1; i <= n; i++)
    {
        int w, v, s;    //物品体积、价值、数量
        cin >> w >> v >> s;
        for (int j = m; j >= w; j--)    //注意:需要添加我们将判断条件提到这一步当中,保证剩余容量大于当前物品的体积
            for (int k = 0; k <= s&&k*w <= j;k++)
                f[j] = max(f[j], f[j - k*w] + k*v);    //对应不选和选
    }

    cout << f[m]<<endl;
    system("pause");
    return 0;
}

和我们前面完全背包问题中的朴素算法类似。

二:多重背包二进制拆分方法

(一)题目要求

简单分析,如果使用方法一,则需要1000×2000×2000=10^9会超时处理。

(二)二进制拆分方法

1到n以内的数字,能够通过 n 内的进制数组合得到,比如 9以内的二进制数有 1 2 4 8,可以自己在草稿纸上试一下, 3能通过 1 + 2 得到,5能通过1 + 4 得到,6能通过 2 + 4得到。

把Ci个物品分解成p+2个,即若干个2的幂次方为系数的体积(对下面的这些体积进行0/1背包):

20*Vi+...+2p*Vi+Ri*Vi  

所以,我们可以利用二进制数的拆分求出所有 n 以内的数;下面是拆分的过程:

9 - 1 = 8;  8 - 2 = 6; 6 - 4 = 2 ; 2 - 8  < 0; 那么要求的 9 以内的二进制数就是  1,2, 4,2,这几个二进制是能够组成 9以内包括 9 的所有整数,然后通过 w[ i ] 乘以这些二进制数,把他们当作一件物品来做01背包的处理。

总之:我们通过上面的二进制拆分方法,我们可以使用log(n)个数表示全部的取值个数范围,虽然我们增加了物品个数<比如第i个物品有9个,转换为1、2、4、2为4个物品,每个物品的体积、价值需要相乘拆分后的个数,这样我们在后面01选取的时候可以选取所有的物品i的所有个数情况>,但是我们将多重背包问题转换为了01背包问题,在后面算法中,我们不需要三层循环处理。复杂度变为:nmlog(num),而不是原来的n*m*num

(三)多重背包问题之二进制拆分算法实现

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 2020;

int n, m;
int f[N];    //全局变量,被初始化0

struct Good{
    int w, v;
};

int main()
{
    vector<Good> goods;
    cin >> n >> m;    //初始化物品数量和背包容积

    for (int i = 1; i <= n; i++)    //初始化二进制拆分后的数量和价值
    {
        int w, v, s;
        cin >> w >> v >> s;
        for (int k = 1; k <= s; k *= 2){    //注意:k从1开始,01背包会进行选或者不选,当所有拆分都不选择,那么就是没有选择这个物品
            s -= k;
            goods.push_back({ w*k, v*k });    //插入当前二进制拆分物品
        }
        if (s > 0)
            goods.push_back({ s*w, s*v });        //插入最后剩余的个数
    }
    //开始进行01背包求解问题
    for (auto g:goods)
        for (int j = m; j >= g.w; j--)    //注意:需要添加我们将判断条件提到这一步当中,保证剩余容量大于当前物品的体积
                f[j] = max(f[j], f[j - g.w] + g.v);    //对应不选和选

    cout << f[m]<<endl;
    system("pause");
    return 0;
}

三:多重背包之单调队列优化问题

(一)题目分析

简单分析,如果使用二进制拆分方法,则需要1000×20000×log(20000)=3×10^8,会出现超时现象

(二)单调队列优化讲解

https://leetcode-cn.com/circle/article/V5qkAa/(不错哦)

https://www.bilibili.com/video/BV1fK4y1b7Xt?from=search&seid=2882629087440342681(也可以)

相比较完全背包问题的状态转移方程:

完全背包,也可以写成下面多重背包的形式,但是最后结束的项表示的是在剩余空间下可选的当前物品数为k,还是有所区别的f(i-1,j-kv)+kw    
这个判断条件是因为完全背包没有对物品进行限制。

多重背包状态转移方程:

都是相对于第i-1层进行的,第一项f(i-1,j)表示没有选择,第二项中f(i-1,j-v)+w表示选择了一个第i种的物品,第三项f(i-1,j-2v)+2w表示选择了两个当前层的物品,.....,最后一项表示了我们最大可以选择的当前层物品数是s个
而s的取值,来自于我们在单调队列中滑动窗口大小的设置。
我们可以发现,求解f(i,j)过程中,max中各项的j,j-v,j-2v,....如果关于对v求余的话,都可以归为j这一组

多重背包状态转移方程变形:

我们后面的循环都是针对这个k进行的。而最终形式如下:

通项如下:由于b在内层循环中为常量,所以我们可以忽略。因此比较最大值时使用第3项即可,更新第i层f值时还是需要使用第一项

单调队列:存放的是k值。注意:我们都是使用i-1层的数据进行更新当前i层的数据

(三)算法实现

 

#include <iostream>
#include <algorithm>
#include <vector>

using namespace std;

const int N = 2020;

int n, m;
int f[N],g[N];    //全局变量,被初始化0
int que[N];    //单调队列

int main()
{
    cin >> n >> m;    //初始化物品数量和背包容积

    for (int i = 1; i <= n; i++)    //初始化二进制拆分后的数量和价值
    {
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);    //备份i-1层的数据信息

        for (int r = 0; r < v;r++)    //循环前面余数部分
        {
            int head = 0, tail = -1;    //设置队列首尾指针
            //开始循环第i-1层同余数r的所有数据,得到第i层信息
            for (int k = 0; r+k*v <= m ;k++)  //r+k*v<m表示没有超过背包容量
            {
                //开始实现单调队列,先了解单调队列
                if (head <= tail && k - que[head] > s)    //如果滑动窗口已满,则出队队首元素---后面判断没有懂????
                    head++;
                while (head <= tail && g[r + k*v] - k*w > g[que[tail] * v + r] - que[tail] * w)    //比较大小,当前元素大于队尾,则队尾出队
                    tail--;
                //开始入队
                que[++tail] = k;    
                //单调队列实现完成
                f[r + k*v] = g[r + que[head] * v] + (k - que[head])*w;    //更新元素,这里k就是图中b,为v的倍数值
            }
        }

    }

    cout << f[m] << endl;
    system("pause");
    return 0;
}

???

            /*
            由于同余的数组元素数量可能超过同一物品的使用次数s。
            k - que[head] > s 实际为 ((r + k * v) - (r + que[head] * v)) / v > s ,((r + k * v)当前遍历到的数组元素下标,(r + que[head] *             v))当前遍历到下标之前的最大数组元素的下标。
            */

 

posted @ 2020-09-08 12:59  山上有风景  阅读(393)  评论(0)    收藏  举报