[NOIP 2006 提高组] 金明的预算方案
附件挂主件,对每个主件枚举附件组合(最多2^2种),转成0/1背包做倒序转移。
OJ: luogu
题目 ID: P1064
难度:普及+/提高-
标签:动态规划01背包分组背包有依赖背包
日期: 2026-08-08 23:13
题意
有
思路
一句话本质:附件不能独立选,必须挂载到主件上。把每个主件及其附件看成一个小组合,枚举附件所有选法后每个组合成为一个 0/1 物品,做标准 0/1 背包。
每个物品不能独立选——先买附件必须买主件,这个约束怎样纳入背包模型?
如果把主件和附件拆开独立处理,就会面临"附件被选但主件没选"的非法状态。更自然的做法是把主件和它的附件捆绑处理:主件必须选,附件可选可不选。
一个主件最多有 2 个附件,这意味着有多少种选择组合?
主件本身有"选"和"不选"两种情况。如果选了主件,附件可以选 0 个、1 个或 2 个。两个附件各有选/不选两种状态,所以选了主件后附件有
因此对每个主件
总价值为:
得到组合后怎么关联到背包上?
每个主件对应一组互斥的选项(主件不选则是空组合,主件选则选一种附件组合)。对于同一主件的不同组合只能选一个——这其实就是分组背包。但因为这里每组只处理一个主件,直接在容量维倒序遍历,对每个容量尝试这组的所有组合即可:
外层遍历每个主件(跳过附件),内层容量
价格都是
这是一个常规模优化:所有价格除以
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 32005;
int n, m;
int v[65], p[65], q[65]; // 价格、重要度、主件编号
vector<int> attach[65]; // attach[i] 表示主件 i 的附件列表
// dp[j] 表示花费 j 元能获得的最大价值(价格 × 重要度)。
int dp[MAXN];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> v[i] >> p[i] >> q[i];
if (q[i] != 0) {
attach[q[i]].push_back(i); // 附件挂到对应主件下
}
}
for (int i = 1; i <= m; i++) {
if (q[i] != 0) continue; // 只处理主件
int sz = attach[i].size();
// 枚举当前主件的所有附件组合(2^sz 种)
for (int j = n; j >= 0; j--) {
for (int mask = 0; mask < (1 << sz); mask++) {
int cost = v[i];
int val = v[i] * p[i]; // 主件必选
bool ok = true;
for (int k = 0; k < sz; k++) {
if (mask >> k & 1) {
int a = attach[i][k];
cost += v[a];
val += v[a] * p[a];
}
}
if (j >= cost) {
dp[j] = max(dp[j], dp[j - cost] + val);
}
}
}
}
cout << dp[n] << '\n';
return 0;
}复杂度
- 物品数
,每个主件最多 个附件 → 最多 种组合 - 总时间复杂度:
- 空间复杂度:
总结
有依赖背包的核心是把"主件→附件"的依赖关系转化为组合枚举。因为附件数量小(