最近在补全提高课所有题目的题解,宣传一下汇总的地方提高课题解补全计划
题目描述
一共有 $N$ 个物品和 $M$ 元钱
物品之间可能存在 依赖关系,对于第 $i$ 个物品,价格为 $v_i$,价值为 $w_i$,依赖的父物品为 $p_i$
每个物品只能被购买一次
依赖关系:只有购买了父物品后,才能购买子物品 (若 $p_i=0$ 表示该物品为 主件)
求一种 购买方案,使得 总花费 不超过 $M$,且 总价值 最大
分析
本题是一道 背包DP 的 经典变种题目:有依赖的背包 DP
根据题设的 拓扑结构 可以观察出每个 物品 的关系构成了 森林
而以往的 背包DP 每个 物品 关系是 任意的(但我们一般视为 线性的)
所以,这题沿用 背包DP 的话,要从原来的 线性DP 改成 树形DP 即可
在 有依赖的背包问题【有依赖背包DP+子物品体积集合划分】 中我们利用了子物品的体积进行 集合划分
时间复杂度是 $O(N \times V \times V)$
但是对于本题 $V$ 的数据范围是 $3.2 \times 10^4$,如果用该种集合 划分方案,毫无疑问会超时
注意到题目中有提到,每个 主件 的 附属品 数量不超过 $2$ 个,且 附物品 不会再有 附属品
因此我们可以采用 分组背包 对本题的状态进行 集合划分
具体思路就是,枚举所有选 子物品 的 组合,每一个 组合 对应一个分组背包中的 物品
这样时间复杂度是 $O(N \times 2^2 \times V)$
闫氏DP分析法:
$f(i,j)$状态表示—集合:考虑前 $i$ 个背包组,且总体积不超过 $j$ 的方案
$f(i,j)$状态表示—属性:方案的总价值 最大MAX
$f(i,j)$状态计算:
$$
f(i,j) = \max\bigg(f(i-1,j), \max\Big(f(i-1,j-v_k)+w_k\Big)\bigg)
\quad
其中k是枚举的物品组i中的物品
$$
Code
时间复杂度: $O(N \times 2^2 \times V)$
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
using namespace std;
const int N = 65, M = 32010;
int n, m;
int w[N], v[N], f[M];
bool not_aff[N];//标记每个分组背包
vector<int> aff[N];//存储每个分组背包内的物品
//dp
void dp(int u, int j)
{
int siz = aff[u].size();
//二进制枚举,列举出所有的分组背包内的物品
for (int st = 0; st < 1 << siz; ++ st)
{
int v_sum = v[u], w_sum = w[u];//必须购买主键后,附件才有价值
for (int i = 0; i < siz; ++ i)
{
if (st >> i & 1)
{
v_sum += v[aff[u][i]];
w_sum += w[aff[u][i]];
}
}
//状态转移
if (v_sum <= j) f[j] = max(f[j], f[j - v_sum] + w_sum);
}
}
int main()
{
//input
cin >> m >> n;
for (int i = 1; i <= n; ++ i)
{
int fa;
cin >> v[i] >> w[i] >> fa;
w[i] *= v[i];
if (fa) aff[fa].push_back(i);
else not_aff[i] = true;//标记分组背包的物品组
}
//dp
for (int i = 1; i <= n; ++ i)
if (not_aff[i]) //分组背包
for (int j = m; j >= 0; -- j)
dp(i, j);
//output
cout << f[m] << endl;
return 0;
}
请问可以不选主键吗
题目里不是说购买附件必须先选主件吗QAQ
是 但是可不可以附件主件都不买QAQ
可以呀,这里状态转移规则用的是01背包的
如果同体积下,价值没有比原来更大,则不会更新
好的大佬tql
nice
按照大佬的思路写了个二维dp,但是有一个数据过不了,大佬们能看看吗
(主要就是改了体积的循环顺序和状态转移方程部分)
感谢!
顿悟
为什么跳过了判别i是否为主件的判断
Orz
。。。。dp思路懂了,二进制枚举不会…
二进制枚举就是因为每组物品数量是2^sv.size,正好是一个次方的关系
大佬if (v_sum <= j) f[j] = max(f[j], f[j - v_sum] + w_sum);为什么不可以放到i的循环里