动态规划——背包问题(二)

三、多重背包问题

问题:
有N种物品和一个容量为v的背包。第i种物品最多有n[i]件可用,每件费用是w[i],价值是c[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包的,且价值总和最大。
基本算法:
这个题目和完全背包问题和类似。基本的方程只需要略微改动即可,因为对于第i种物品有n[i]+1种策略:取0件,取1件……取n[i]件。令f[i][v]表示前i种物品恰放入一个容器为v的背包的最大权值,则:f[i][v]=max{f[i-1][v-k*w[i]]+k*c[i]|0<=k<=n[i]}。复杂度是O(V*∑n[i])。
例题:庆功会
【问题描述】为了庆贺班级在校运动会上取得全校第一的成绩,班主任决定开一场庆功会,为此拨款购买奖品犒劳运动员。期望拨款金额能够购买最大价值的奖品,可以补充他们的精力和体力。
【输入格式】
第1行两个数n(n<=500),m(m<=6000),其中n代表希望购买的奖品的种数,m表示拨款金额。
接下来n行,每行3个数,v、w、s分别表示第i种奖品的价格、价值(价格与价值是不同的概念)和能购买的最大数量(买0件到买s件均可),其中v<=100,w<=1000,s<=10。
【输出格式】
一个数,表示此次购买能获得的最大的价值(注意!不是价格)。
【输入样例】

5 1000
80 20 4
40 50 9
30 50 7
40 30 6
20 20 1

【输出样例】

1040

代码如下:

#include<iostream>
#include<cstdio>
#include<string.h> 
#include<algorithm>
using namespace std;
int v[6001],w[6001],s[6001],f[6001],n,m;

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&v[i],&w[i],&s[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--){
            for(int k=0;k<=s[i];k++){
                if(j-k*v[i]<0){
                    break;
                }
                f[j]=max(f[j],f[j-k*v[i]]+k*w[i]);
            }
        }
    }
    printf("%d",f[m]);
    return 0;
}

如何优化:
转化为01背包问题,另一种好想有好写的基本方法就是转化为01背包问题求解:把第i种物品换成n[i]件01背包中的物品,则得到了物品数为∑n[i]的01背包问题,直接求解,复杂度仍然是O(V*∑n[i])。但我们期望将它转化为01背包问题之后能够像完全背包一样降低复杂度。仍然考虑二进制的思想,我们考虑把第i种物品换成若干件物品,是的原问题种第i种物品可取的每种策略——取0..n[i]件——均能等价于取若干件代替后的物品。另外,取超过n[i]件的策略必不能出现。
方法是:将第i种物品分成若干件物品,其中每件物品都有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数,使这些系数分别为1,2,4,…,2^k+1,且k满足n[i]-2^k+1>0的最大整数(注意:这些系数已经可以组合出1~n内的所有数字)。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2^k-1和2*k..n[i]两段分别讨论得出。
这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*∑log n[i])的01背包问题,是很大的改进。
二进制优化代码:

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
int v[6001],w[6001],f[6001],n,m,n1,x,y,s,t=1;

int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&x,&y,&s);
        while(s>=t){
            v[++n1]=x*t;
            w[n1]=y*t;
            s-=t;
            t*=2;
        }
        v[++n1]=x*s;
        w[n1]=y*s;//s2的指数分堆:1,2,4,...,2^(k-1),s-2^k+1; 
    }
    for(int i=1;i<=n1;i++){
        for(int j=m;j>=v[i];j--){
            f[j]=max(f[j],f[j-v[i]]+w[i]);
        }
    }
    printf("%d",f[m]);
    return 0;
}

四、混合三种背包问题

问题:
如果将01背包,完全背包,多重背包混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢。
01背包与完全背包的混合:
考虑到01背包和完全背包中最后给出的伪代码只有一处不同,故如果只有两类物品:一类物品只能取一次 ,另一类物品可以取无限次,那么只需要在对每个物品应用转移方程时,根据物品的类别选用顺序或逆序的循环即可,复杂度是O(VN)
伪代码如下:

for i=1..N
  ifi件物品是01背包
    for v=V..0
      f[v]=max{f[v],f[v-w[i]]+c[i]};
  else ifi件物品是完全背包
    for v=0..V
      f[v]=max{f[v],f[v-w[i]]+c[i]};
  再加上多重背包

如果再加上有的物品最多可以取有限次,那么原则上也可以给出O(VN)的解法:遇到多重背包类型的物品用单调队列解即可。但如果不考虑超过Noip范围的算法的话,用多重背包中将每个这类物品分为O(log n[i])个01背包的物品的方法也已经很优了。
例题、混合背包
【问题描述】
一个旅行者有一个最多能装V公斤的背包,现在有n件物品,它们的重量分别是W1,W2,…Wn,它们的价值分别为C1,C2,…,Cn。有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取得次数有一个上限(多重背包)。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
【输入格式】
第1行:两个整数,M(背包容量,M<=200),N(物品数量,N<=30);
第2行至N+1行:每行三个整数Wi,Ci,Pi,前两个整数分别表示每个物品的重量,价值,第三个整数若为0,则说明此物品可以购买无数件;若为其他数字,则为此物品可购买的最多件数(Pi)。
【输出格式】
仅一行,一个数,表示最大总价值。
【输入样例】

10 3
2 1 0
3 3 1
4 5 4

【输出样例】

11

【样例说明】
选第一件物品1件和第三件物品2件。
代码如下:

#include<iostream>
#include<cstdio>
#include<string.h>
#include<algorithm>
using namespace std;
int w[31],c[31],p[31],f[201],m,n;

int main(){
    scanf("%d%d",&m,&n);
    for(int i=1;i<=n;i++){
        scanf("%d%d%d",&w[i],&c[i],&p[i]);
    }
    for(int i=1;i<=n;i++){
        if(p[i]==0){               //完全背包问题 
            for(int j=w[i];j<=m;j++){
                f[j]=max(f[j],f[j-w[i]]+c[i]);
            }
        }
        else{                       //01背包和多重背包问题   
            for(int j=1;j<=p[i];j++){
                for(int k=m;k>=w[i];k--){
                    f[k]=max(f[k],f[k-w[i]]+c[i]);
                }
            }
        }
    }
    printf("%d",f[m]);
    return 0;
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
动态规划是一种将大问题分解为小问题进行解决的方法,背包问题动态规划中最经典的题型之一。背包问题分为三类:01背包、完全背包和多重背包。其中,01背包问题是最经典的背包问题,也是动态规划的入门级必学算法。\[1\] 动态规划解决背包问题的核心思想是将问题分解为若干个小问题,先求解子问题,然后从子问题的解得到原问题的解。与分治法不同的是,动态规划适用于有重叠子问题的情况,即下一阶段的求解是建立在上一阶段的解的基础上进行进一步求解。通过填表的方式,逐步推进,最终得到最优解。\[2\] 多重背包问题介于01背包和完全背包之间,可以将其转化为01背包或完全背包问题来求解。对于某种物品,如果其数量乘以单位体积大于背包总容量,那么该物品与背包之间是完全背包问题。而对于某种物品,可以将其数量视为不同的物品,然后按照01背包问题进行处理。这样的转化可以在数据范围较小时适用,但在数量较大时可能会导致超时。因此,可以采用更精炼的划分方案,如进制拆分,来减少物品分类的组数,从而优化算法的效率。\[3\] 总结来说,动态规划是一种解决背包问题的有效方法,通过将大问题分解为小问题,并利用子问题的解来求解原问题,可以得到背包的最优解。 #### 引用[.reference_title] - *1* *3* [【算法与数据结构】—— 动态规划背包问题](https://blog.csdn.net/the_ZED/article/details/104882665)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [动态规划算法解决经典背包问题](https://blog.csdn.net/m0_52110974/article/details/120122061)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值