1、P1064 金明的预算方案
题目描述
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 N 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:
主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅 无
如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 0 个、1 个或 2 个附件。附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 N 元。于是,他把每件物品规定了一个重要度,分为 5 等:用整数 1−5 表示,第 5 等最重要。他还从因特网上查到了每件物品的价格(都是 10 元的整数倍)。他希望在不超过 N 元(可以等于 N 元)的前提下,使每件物品的价格与重要度的乘积的总和最大。
设第jj件物品的价格为 v[ j],重要度为 w[ j],共选中了 k 件物品,编号依次为 j1, j2, … , jk,则所求的总和为:
v[ j1] × w[ j1] + v[ j2] × w[ j2] + … + v[ jk] × w[ jk]。
请你帮助金明设计一个满足要求的购物单。
输入格式
第 1 行,为两个正整数,用一个空格隔开:
N m (其中 N(<32000) 表示总钱数,m(<60) 为希望购买物品的个数。) 从第 2 行到第 m+1 行,第 j 行给出了编号为 j-1 的物品的基本数据,每行有 3 个非负整数
v p q (其中 v 表示该物品的价格(v<10000),p 表示该物品的重要度(1−5),q 表示该物品是主件还是附件。如果 q=0,表示该物品为主件,如果 q>0,表示该物品为附件,q 是所属主件的编号)
输出格式
一个正整数,为不超过总钱数的物品的价格与重要度乘积的总和的最大值(<200000)。
输入#1
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出#1
2200
题目链接:https://www.luogu.org/problem/P1064
2、题解-敲代码
(1)组内01背包,对附件进行01背包处理,每组生成多个新的 “ 组合物品 ”,包括(主件)、(主件+若干附件)。
(2)分组背包处理,从每组中至多选择一个 ” 组合物品 “。
/*
输入:
背包容量 物品数量
物品价格 重要度 依赖(0是主件,>0是附件,代表依赖于第几个物品(从1开始编号))
物品价格 重要度 依赖
物品价格 重要度 依赖
输出:
所选每件物品的价格与重要度的乘积的总和最大
输入1:
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
输出1:
2200
输入2:
10 5
8 2 0
4 5 1
3 5 1
4 3 0
5 2 0
输出2:
22
*/
#include <iostream>
#include <vector>
#include <map>
#include <climits>
using namespace std;
int main()
{
int n; //背包容量
int m; //物品数量,编号从1开始
cin>>n>>m;
vector<int> v(m+1); //重量
vector<int> p(m+1); //收益 = 重量 * 重要度
vector<int> q(m+1); //依赖
map<int, vector<int>> mp; //主件,附件编号序列
for(int i=1; i<=m; i++)
{
cin >> v[i] >> p[i] >> q[i];
p[i] = p[i] * v[i]; //收益 = 重量 * 重要度
//将附件加入主件的附件序列
if(q[i]>0)
mp[q[i]].push_back(i);
}
//存放每组,“组合物品”
vector<vector<int>> mpv; //每个下标为一组,每组有效组合的,重量序列
vector<vector<int>> mpp; //每个下标为一组,每组有效组合的,收益序列
//对所有主件,进行01处理,恰好
for(int i=1; i<=m; i++)
{
//跳过附件
if(q[i]>0) continue;
//对除本主件之外的所有附件01处理
int nn = n-v[i]; //最大容量,除去主件
vector<int> dp(nn+1, INT_MIN); //恰好初始化
dp[0] = 0;
for(auto k : mp[i]) //对每个附件
{
for(int j=nn; j>=v[k]; j--) //对不同容量
{
dp[j] = max(dp[j], dp[j-v[k]] + p[k]);
}
}
//记录本组(主件i)的有效“组合物品”,(主件i)或者(主件i+若干附件)
vector<int> tv; //本组“组合物品”,重量序列
vector<int> tp; //本组“组合物品”,收益序列
//将有效组合结果,加入有效组合数组
for(int j=0; j<=nn; j++)
{
if(dp[j]<0) continue; //负无穷,无效,不能恰好
tv.push_back(j + v[i]); //记录有效组合的重量
tp.push_back(dp[j] + p[i]); //记录有效组合的收益
}
//只考虑主件i,其实已经包含在上面。当j=0的时候,dp[j]=0。由于后续分组处理中,每组只选一个组合物品,因此,此处加不加都可以。
//tv.push_back(v[i]);
//tp.push_back(p[i]);
//本组的“组合物品”重量、收益,插入
mpv.push_back(tv);
mpp.push_back(tp);
}
//分组处理,每个分组只取一个组合物品
vector<int> dp2(n+1, 0);
for(int k=0; k<(int)mpv.size(); k++) //对于每个分组
{
for(int j=n; j>=0; j--) //容量
{
for(int i=0; i<(int)mpv[k].size(); i++) //组内成员(组内组合物品)
{
int vv = mpv[k][i]; //新物品重量
int pp = mpp[k][i]; //新物品收益
if(j >= vv)
dp2[j] = max(dp2[j], dp2[j-vv] + pp);
}
}
}
cout << dp2[n] << endl;
return 0;
}