动态规划 选择dp:多重背包+多重背包puls----中专生刷算法

不了解动态规划和选择dp的同学

先看一下这两篇文章

动态规划:选择dp及优化01背包问题-CSDN博客

动态规划:完全背包问题----中专生刷算法-CSDN博客

然后我们来做题

普通题+进阶题,图文详解,化零为整的解决多重背包puls问题!!!

多重背包

输入格式

输出格式

输出一个整数,表示最大价值。

数据范围

输入样例
4 5
1 2 3
2 4 1
3 4 3
4 5 2
输出样例:
10

这题实际上是完全背包问题和01背包问题的结合
N的范围是<=100

O(N^3)的时间复杂度就可以通过

我们改编一下完全背包问题的闫氏DP分析法,即可

这是原来的

改编

在状态计算最后一行不同

代码实现
#include<iostream>
#include<cmath>
using namespace std;
int u[110],w[110],s[110];
int f[110][110];
int main(){
    int N,V;
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        cin>>u[i]>>w[i]>>s[i];
    for(int i=1;i<=N;i++){
        for(int j=1;j<=V;j++){
            //枚举每次选对于第i个数选几个是最优解
            //k<=s[i]&&k*u[i]<=j保证不越界
            for(int k=0;k<=s[i]&&k*u[i]<=j;k++){
                f[i][j]=max(f[i][j],f[i-1][j-k*u[i]]+k*w[i]);
            }
        }
    }
    cout<<f[N][V];
    return 0;
}

多重背包问题puls

多重背包问题puls,在多重背包的基础上,在题目上没有任何改变

但是增加了多重背包问题的数据范围

题目来自acwing

我们仔细想一下上面的做法

实际上上面是暴力的把数量为s的物品i

转成了同价值的,同容量的s个物品

例如,我们看一下样例和样例展开图

原来输入的是这样的

我们在处理时,实际上是这样的

这样在s[i]太大时,我们处理数据量就太大了

这种算法太过暴力

为了优化这种算法

我们引入一种新的算法概念

二进制优化

我们每次都暴力枚举一个数,我们在思考,有没有办法,可以减少枚举的次数

我们来看一个图

此图已被我修改更好理解

原图的来源:哔哩哔哩一只小傲风

我们仔细观察二进制数

会得出一个惊人但是本该如此的理论

这个十个编号,对应的二进制数

能组成任意一个十位二进制数

能组成任意一个十位二进制数等价于能组成任意一个小于1024的数字

在题目数据范围中,s[i]小于2000

随便举一个例子,例如1888

那我们可以把1888这个数的前1023,都分成如上分

然后最后一份为1888-1023=865

如果我们要组成1866

那我们可以使用1+8+32+64+128+256+512+865=1866

也就是我们可以拿11个编号的数,组成2000以内任意数

在暴力算法下,本来要枚举两千次的计算量

直接优化为了11次

本来我们的暴力算法,是等价于,把s[i]个数量的物品拆分成s[i]个

现在我们拆分s[i]个物品,至多拆分为11个!!!

所以夸张的说,时间复杂度最高可降低一千倍

在读入数据的时候,把s[i]件物品i分割成至多11件物品组

可降低时间复杂度,从而通过代码

代码实现
#include<iostream>
using namespace std;

const int N = 12010, M = 2010;

int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 体积<M

int main()
{
    cin >> n >> m;
    int cnt = 0; //分组的组别
    for(int i = 1;i <= n;i ++)
    {
        int a,b,s;
        cin >> a >> b >> s;
        int k = 1; // 组别里面的个数
        while(k<=s)
        {
            cnt ++ ; //组别先增加
            v[cnt] = a * k ; //整体体积
            w[cnt] = b * k; // 整体价值
            s -= k; // s要减小,代表其他元素已经建组
            k *= 2; // 组别里的个数增加,换二进制表示
        }
        //检测是否剩余,新建一组
        if(s>0)
        {
            cnt ++ ;
            v[cnt] = a*s; 
            w[cnt] = b*s;
        }
    }

    n = cnt ; //枚举次数由个数变成组别

    //直接当01背包模板题写
    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]);

    cout << f[m] << endl;
    return 0;
}

问题:为什么不会影响最后结果

因为11个组别可以组成2000以内任意数,也就是枚举这11个组

和枚举2000个数,能得到的全部结果是一样的

所以能得到的最优解,价值最大的方案也是一样的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值