购物单

购物单_牛客网
在这里插入图片描述
在这里插入图片描述

是比较简单的有依赖的背包问题,这儿附件最多只有两个,所以枚举起来也方便。

不管是什么样的背包问题变体,一般都是转化为01背包问题进行处理的。先来思考依赖问题,附件不能够单独选,只能和主件搭配在一起,那么本题中对于一个主件,有四种搭配方式:

只选主件,主件+附件1,主件+附件2,主件+附件1+附件2

所以也就只是01背包问题多了几种选择而已。直接写01背包的一维dp:

#include <bits/stdc++.h>
using namespace std;
int n, m;
int v[65][3], w[65][3];//第二个维度3代表主件、附件1、附件2
int dp[3200];

int main(){
    cin >> n >> m;//钱的总数和物品数量
    n /= 10;//价格是10的整数倍
    int x, p, q;
    for(int i = 1; i <= m; ++i){ //m个物品
        cin >> x >> p >> q;
        x /= 10; p *= x;//p变为价格*重要程度
        if(q == 0){//是主件
            v[i][0] = x; w[i][0] = p;
        }else{//是附件,添加到主件里面去(附件是不能单独选的)
            if(v[q][1] == 0){//第一个附件
                v[q][1] = x; w[q][1] = p;
            }else{//第二个附件(最多两个附件)
                v[q][2] = x; w[q][2] = p;
            }
        }
    }

    for(int i = 1; i <= m; ++i){
        for(int j = n; j >= v[i][0]; --j){//01背包的经典逆序,v[i][0]是最少需要的容量
            int a1 = v[i][0], b1 = w[i][0];//价格、价格*重要程度
            int a2 = v[i][1], b2 = w[i][1];//附件1
            int a3 = v[i][2], b3 = w[i][2];//附件2
            //一共四种选择:只选主件,主件+附件1,主件+附件2,主件+附件1+附件2,当然也可以不选
            if(j >= a1)    dp[j] =  max(dp[j], dp[j-a1] + b1);//不选或者只选主件
            if(j >= a1+a2)    dp[j] =  max(dp[j], dp[j-a1-a2] +b1+b2);//主件 或者 主件+附件1
            if(j >= a1+a3)    dp[j] =  max(dp[j], dp[j-a1-a3] +b1+b3);//主件 或者 主件+附件2
            if(j >= a1+a2+a3)    dp[j] =  max(dp[j], dp[j-a1-a2-a3] +b1+b2+b3);//主件 或者 主件+附件1+附件2
        }
    }
    cout << dp[n]*10 << endl;
    return 0;
}

不过我在解题的过程中也出了一个错,一开始我是想先把主件+附件的组合枚举出来(反正也不多),得到新的物品组,然后再进行01背包dp,代码是这样:

#include <bits/stdc++.h>
using namespace std;
const int N = 250;//一个主件最多2个附件,主件、附件的搭配最多四种
int n, m;
int a[65][3], b[65][3];//第二个维度3代表主件、附件1、附件2
int v[N], w[N], dp[3200]; //第二个维度

int main(){
    cin >> n >> m;//容量、物品数
    n /= 10;
    for(int i = 1; i <= m; ++i){
        int v, p, q;//体积、重要度、主件或附件
        cin >> v >> p >> q;
        v /= 10; p *= v;//分别代表体积、价值
        if(q == 0){//主件
            a[i][0] = v;
            b[i][0] = p;
        }else{
            if(a[q][1] == 0){//附件1
                a[q][1] = v;
                b[q][1] = p;
            }else{//附件2
                a[q][2] = v;
                b[q][2] = p;
            }
        }
    }
    int idx = 0;
    for(int i = 1; i <= m; ++i){//生成新的01背包的物品组
        if(a[i][0] == 0)    continue;//是附件
        v[idx] = a[i][0]; w[idx++] = b[i][0];//主件
        if(a[i][1] == 0)    continue;
        v[idx] = a[i][0] + a[i][1];//主件+附件1
        w[idx++] = b[i][0] + b[i][1];
        if(a[i][2] == 0)    continue;
        v[idx] = a[i][0] + a[i][2]; //主件+附件2
        w[idx++] = b[i][0] + b[i][2];
        v[idx] = a[i][0] + a[i][1] + a[i][2]; //主件+附件1+附件2
        w[idx++] = b[i][0] + b[i][1] + b[i][2];
    }
    for(int i = 0; i < idx; ++i){//01背包
        for(int j = n; j >= v[i]; --j){
            dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
        }
    }
    cout << dp[n]*10 << endl;
    return 0;
}

思路咋看没有问题,但是并不能通过全部的测试用例,得到的结果总是会大于等于正确的结果。出错的原因是,没有保证主件+附件的各种组合之间是互斥的,详细解释一下:

  • 最开始的正确代码,是在主件+附件的四种搭配,和一个都不选,这五种选择中选择一种,选了某一种之后就不能再选其他的搭配了。比如选择了主件+附件1,那么主件+附件2就不会再被选中了,保证了组内各种搭配的互斥关系。
  • 而上面错误的代码,是先生成了可选的所有搭配,再进行01背包的dp,但是dp过程中却无法保证一个主件的多个搭配之间的互斥关系。这点很好理解,因为建立新的物品组的时候,并没有做任何能够将一组内的搭配分开来的操作,只是简单的进行添加而已。单独的主件1被选取之后,后续的主件1+附件1仍然有机会被选取,无法保证互斥。

所以,要想事先生成可选物品组的话,应该按照分组背包的思路来组织,将一个主件的多个搭配放在一个组内,dp的时候保证组内的互斥关系。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值