是比较简单的有依赖的背包问题,这儿附件最多只有两个,所以枚举起来也方便。
不管是什么样的背包问题变体,一般都是转化为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的时候保证组内的互斥关系。