原题链接:https://www.luogu.com.cn/problem/P1064
难度:普及+/提高
涉及知识点:分组背包,有依赖的背包问题,动态规划
金明的预算方案一题是“有依赖的背包问题”的始祖,正因为有了这题才有了它。
有依赖的背包问题
如果读者是较早看到这篇博文的,那么我可能还没有放我的学习笔记,当然笔者会尽快地更新。
题意分析
给定 m m m 组物品,每组物品包括主件和附件,选择了某个附件必须选择其所依附的主件,求在不超过给定的限制代价 m m m 下选择出来的最大价值。这就是极典型的有依赖的背包问题。
分析与解决
此题难在难在数据处理,考虑用
p
a
i
r
<
i
n
t
,
i
n
t
>
pair<int, int>
pair<int,int> 存储所有主件的信息,用
v
e
c
t
o
r
vector
vector 存储每个主件的附件信息。
然后我们思考如何解决问题。可以这样思考,因为选择了某组物品的某个附件那就必须选择它的主件,附件选择的个数是自由决定的,那么我们就可以用 1 个主件和若干个附件组合起来,搭配成一个物品,这样,每组物品中就存在可以直接选择的若干物品了,我们只需要选择其中一种搭配就行了。显而易见的,有依赖的背包问题就被我们转化成了分组背包问题。
这里有个问题,每组有多少个搭配呢?我们可以转化为树形结构来直观计算,以下图为例:
分类讨论一下:
- 如果选 0 个,那么搭配的数量为 C 3 0 = 1 C_3^0 = 1 C30=1.
- 如果选 1 个,那么搭配的数量为 C 3 1 = 3 C_3^1 = 3 C31=3.
- 如果选 2 个,那么搭配的数量为 C 3 2 = 3 C_3^2 = 3 C32=3.
- 如果选 3 个,那么搭配的数量为
C
3
3
=
1
C_3^3 = 1
C33=1.
加起来的总数为8,也就是 2 3 2^3 23 ,有没有发现,如果令某个主件的附件有 k k k 个,那么能够搭配出来的方案数就是 2 k 2^k 2k,用 v e c t o r vector vector 存附件也是因为便于得到附件个数。其实这就是搭配方案数的一个数学性质。有什么用呢,在枚举决策时用的,当然在枚举决策时会减去1,因为选0个是没有意义的。
既然转化为了分组背包问题,那么就要讨论一下处理好数据后的核心部分了。我们都知道分组背包问题分为枚举物品、代价、决策三部分。
- 对于物品层,我们自然是针对主件讨论的,枚举到某个主件时,如果这个主件的代价为 0,那么它的价值也应该是 0,这个主件就是没有意义的,可以直接跳过。
- 对于代价层,按照常规的从大到小即可。
- 对于决策层,令某个主件的附件个数为
s
v
sv
sv,从
0
0
0 枚举到
1
<
<
s
v
−
1
1 << sv - 1
1<<sv−1。先提前把主件的代价和价值提出来,然后枚举每一个附件,对于一个附件下标
u
u
u,如果在当前决策
k
k
k 的二进制状态下的第
u
u
u 位为 0,则需要选择,即
k >> u & 1
。需要选择的附件就把这个它的代价和价值都累加到先前准备的主件的代价和价值上。在全部附件枚举完成后,再进行状态转移,状态转移的方程为 -
f
[
j
]
=
max
(
f
[
j
]
,
f
[
j
−
v
]
+
w
)
f[j] =\max(f[j],f[j-v]+w)
f[j]=max(f[j],f[j−v]+w)
完毕。
AC代码
#include <iostream>
#include <cstdio>
#include <vector>
using namespace std;
typedef pair<int, int> PII;
#define v first
#define w second
const int N = 3e5;
int n, m;
PII master[N];
vector<PII> servant[N];
int f[N];
int main()
{
cin >> m >> n;
for (int i = 1; i <= n; i++)
{
int v, w, q;
cin >> v >> w >> q;
if (!q) master[i] = {v, v * w};
else servant[q].push_back({v, v * w});
}
for (int i = 1; i <= n; i++) //枚举物品
{
if (master[i].v)
{
for (int j = m; j >= 0; j--)
{
auto &sv = servant[i];
for (int k = 0; k < 1 << sv.size(); k++)
{
int v = master[i].v, w = master[i].w;
for (int u = 0; u < sv.size(); u++)
{
if (k >> u & 1)
{
v += sv[u].v;
w += sv[u].w;
}
}
if (j >= v) f[j] = max(f[j], f[j - v] + w);
}
}
}
}
cout << f[m];
return 0;
}