Problem Statement
金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过 $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$,表示该物品本身是主件。
Output
输出一行一个整数表示答案。
1
2
3
4
5
6
|
1000 5
800 2 0
400 5 1
300 5 1
400 3 0
500 2 0
|
Sample Output
Constraints
对于全部的测试点,保证 $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$。
Solving
有依赖的背包模板题
有依赖的背包,意为部分物品的选取依赖于某个特定种类的物品,且各个种类间的物品选择关系互斥。此时则变成了特殊的01背包,即有依赖的背包,此时我们讨论的关键不再是要或不要,而是 不要 或 怎么要
所以做题整体的思路为,读入数据,标记每个被依赖的数据,记录依赖当前物品的个数,同时也记录那些物品依赖当前被依赖的物品,随后进行01背包操作:
每遍历到一个被依赖的物品,围绕此物品展开,获得所有依赖此物品的物品,再在其中作选择(不要或怎么要)。
以下是不进行一维空间压缩的程序代码,
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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
//有依赖的背包,环绕不要和怎么要的问题。
#include <bits/stdc++.h>
#define int long long
#define endl '\n'
#define INF 0x3f3f3f3f
#define NINF -0x3f3f3f3f
#define Single
using namespace std;
using ll = long long;
typedef pair<int,int> pii;
typedef pair<ll,ll> pll;
const int N = 32010;
const int M = 62;
bitset<M> is_major;
int followers[M][2];
int followcnt[M];
int cost[M];
int val[M];
void solve()
{
int n,m;
cin >> n >> m;
for(int i=1;i<=m;i++)
{
int v,p,q;
cin >> v >> p >> q;
if(q==0) is_major[i] = true;
else
{
followers[q][followcnt[q]] = i;
followcnt[q]++;
}
cost[i] = v;
val[i] = v * p;
}
int dp[m+1][n+1];
memset(dp,0,sizeof dp);
int ptr = 0;//由于不是每个物品都讨论拿或不拿所以需要一个指针,指向上一个需要展开讨论的主要物品,即二维dp表内的上一行.
for(int i=1,fan1,fan2;i<=m;i++)
{
if(is_major[i])
{
for(int j=0;j<=n;j++)
{
//要法1 主附都不要 直接复制上一个主物品下的数值
dp[i][j] = dp[ptr][j];
//要法2 选择要不要主
if(j >= cost[i])
{
dp[i][j] = max(dp[i][j],dp[ptr][j-cost[i]]+val[i]);
}
//两个附件,没有则初始化为-1
fan1 = followcnt[i] >= 1 ? followers[i][0] : -1;
fan2 = followcnt[i] >= 2 ? followers[i][1] : -1;
//要法3 选择主的情况下 选择附1
if(j >= cost[i] + cost[fan1]&&fan1 != -1)
{
dp[i][j] = max(dp[i][j],dp[ptr][j-cost[i]-cost[fan1]]+val[i]+val[fan1]);
}
//要法4 选择主的情况下 选择附2
if(j >= cost[i] + cost[fan2]&&fan2 != -1)
{
dp[i][j] = max(dp[i][j],dp[ptr][j-cost[i]-cost[fan2]]+val[i]+val[fan2]);
}
//要法4 选择主的情况下 两个附件都要
if(fan1 != -1 && fan2 != -1 && j - cost[i] - cost[fan1] - cost[fan2] >= 0)
{
dp[i][j] = max(dp[i][j],dp[ptr][j-cost[i]-cost[fan1]-cost[fan2]]+val[i]+val[fan1]+val[fan2]);
}
}
ptr = i ;//更新指针指向当前行
}
}
cout << dp[ptr][n];
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);
int T;
#ifdef Single
T = 1;
#else
cin >> T;
#endif
while(T--)
{
solve();
}
return 0;
}
|
一下是进行过空间优化后的一维DP程序片段,由于dp表中自动继承上一行的数值,所以不再需要指针ptr:
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
|
int dp[n+1];
memset(dp,0,sizeof dp);
for(int i=1;i<=m;i++)
{
if(is_major[i])
{
for(int j=n;j>=cost[i];j--)
{
dp[j] = max(dp[j],dp[j-cost[i]]+val[i]);
int fan1 = followcnt[i] >= 1 ? followers[i][0] : -1;
int fan2 = followcnt[i] >= 2 ? followers[i][1] : -1;
if(j>=cost[i] + cost[fan1]&&fan1 != -1)
{
dp[j] = max(dp[j],dp[j-cost[i]-cost[fan1]]+val[i]+val[fan1]);
}
if(j>=cost[i]+cost[fan2]&&fan2 != -1)
{
dp[j] = max(dp[j],dp[j-cost[i]-cost[fan2]]+val[fan2]+val[i]);
}
if(j>=cost[i]+cost[fan1]+cost[fan2]&&fan1 != -1 && fan2 != -1)
{
dp[j] = max(dp[j],dp[j-cost[i]-cost[fan1]-cost[fan2]]+val[i]+val[fan1]+val[fan2]);
}
}
}
}
cout << dp[n];
|