[NOIP2006 提高组] 金明的预算方案

题目描述

金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 $n$ 元钱就行”。今天一早,金明就开始做预算了,他把想买的物品分为两类:主件与附件,附件是从属于某个主件的,下表就是一些主件与附件的例子:

主件 附件
电脑 打印机,扫描仪
书柜 图书
书桌 台灯,文具
工作椅

如果要买归类为附件的物品,必须先买该附件所属的主件。每个主件可以有 $0$ 个、$1$ 个或 $2$ 个附件。每个附件对应一个主件,附件不再有从属于自己的附件。金明想买的东西很多,肯定会超过妈妈限定的 $n$ 元。于是,他把每件物品规定了一个重要度,分为 $5$ 等:用整数 $1 \sim 5$ 表示,第 $5$ 等最重要。他还从因特网上查到了每件物品的价格(都是 $10$ 元的整数倍)。他希望在不超过 $n$ 元的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第 $j$ 件物品的价格为 $v_j$,重要度为$w_j$,共选中了 $k$ 件物品,编号依次为 $j_1,j_2,\dots,j_k$,则所求的总和为:

$v_{j_1} \times w_{j_1}+v_{j_2} \times w_{j_2}+ \dots +v_{j_k} \times w_{j_k}$。

请你帮助金明设计一个满足要求的购物单。

输入格式

第一行有两个整数,分别表示总钱数 $n$ 和希望购买的物品个数 $m$。

第 $2$ 到第 $(m + 1)$ 行,每行三个整数,第 $(i + 1)$ 行的整数 $v_i$,$p_i$,$q_i$ 分别表示第 $i$ 件物品的价格、重要度以及它对应的的主件。如果 $q_i=0$,表示该物品本身是主件。

输出格式

输出一行一个整数表示答案。

样例 #1

样例输入 #1

1
2
3
4
5
6
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0

样例输出 #1

1
2200

提示

数据规模与约定

对于全部的测试点,保证 $1 \leq n \leq 3.2 \times 10^4$,$1 \leq m \leq 60$,$0 \leq v_i \leq 10^4$,$1 \leq p_i \leq 5$,$0 \leq q_i \leq m$,答案不超过 $2 \times 10^5$。

题解

题目解析

还是一道背包题,这道题说实话如果真的是理解了的话,其实也非常简单.

题目中说了一个主件最多有两个附件,我们使用一维滚动来存这个dp数组.简单分析一下状态转移方程,dp[j]可以由几种方式组成呢?

  • dp[j]可以由之前的dp[j],也就是没有拿东西转移而来
  • dp[j]可以由拿一个主件转移而来
  • dp[j]可以由拿一个主件和另外两个附件的任意一个转移而来
  • dp[j]可以由拿一个主件和另外两个附件一共三个转移而来

于是我们就得到了这样的思路,我们把这个思路转换为状态转移方程.那就是:

剩余细节会在代码中详细写出.

AC代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <cstring>
#include <cmath>
#include <algorithm>
#define endl "\n"
using namespace std;

typedef long long ll;
const int MAXN = 60+10;

int v1[MAXN],cost1[MAXN],v2[MAXN][5],cost2[MAXN][5],dp[32100];

int main(){
ios::sync_with_stdio(0);
cin.tie(0);
int n,m;
cin >> n >> m;
for(int i=1;i<=m;i++){
int v,p,q;
cin >> v >> p >> q;
if(!q){
v1[i] = v;
cost1[i] = v * p;
}else{
v2[q][0]++;
v2[q][v2[q][0]] = v;
cost2[q][v2[q][0]] = v * p;
}
}
for(int i=1;i<=m;i++){
for(int j=n;v1[i]>0&&j>=v1[i];j--){
int temp = 0;
temp = max(temp,dp[j]);//没拿东西直接转移过来
temp = max(temp,dp[j-v1[i]]+cost1[i]);//拿一个主件
if(j-v1[i]-v2[i][1] >= 0)//如果可以由一个主件和第一个附件转移
temp = max(temp,dp[j-v1[i]-v2[i][1]]+cost2[i][1]+cost1[i]);
if(j-v1[i]-v2[i][2] >= 0)//如果可以由一个主件和第二个附件转移
temp = max(temp,dp[j-v1[i]-v2[i][2]]+cost2[i][2]+cost1[i]);
if(j-v1[i]-v2[i][2]-v2[i][1] >= 0)//如果可以由一个主件和两个个附件一起转移
temp = max(temp,dp[j-v1[i]-v2[i][2]-v2[i][1]]+cost2[i][2]+cost2[i][1]+cost1[i]);
dp[j] = temp;
}
}
cout << dp[n] << endl;
return 0;
}