LOJ #160. 树形背包 题解

Description

NN 个物品,编号分别为 1N1\ldots N。物品 ii 的重量为 wiw_i,价值为 viv_i

给出每个物品依赖于哪个物品。我们用 di=j (i>j>0)d_i = j\ (i>j>0) 表示:如果要选取物品 ii,就必须先选取物品 jj。另外,我们用 di=0(i>0)d_i = 0 (i>0) 表示:该物品不依赖于任何物品。保证每个物品最多只依赖一个物品。保证依赖关系合理,不会出现环。

背包最多能装载的重量为 WW,请问背包中最多能装入多大价值的物品。

1N×W6×107,1N5×104,0W6×1041\le N×W\le 6×10^7, 1\le N\le 5×10^4, 0\le W\le 6×10^4

1wi200,0vi50001\le w_i\le 200,0\le v_i\le 5000

Solution

题目的限制相当于是要求出一个包含根的连通块使得重量之和不超过 WW,并且让价值之和最大。

可以设 fi,jf_{i,j} 表示 ii 的子树里包含 ii 的连通块重量之和为 jj 的最大价值。容易做到 O(NW2)O(NW^2)

上面那个做法慢在要做 NN 次背包合并,单次背包合并就是 O(W2)O(W^2) 的,所以要找到一个不需要背包合并的做法。

考虑按照 dfs 序从后往前 dp。

假设当前枚举到了 xx,则把 dfs 序大于等于 xx 的点拿出来一定构成若干个子树,并且这些子树互不相交。如果在这些点中选出一些不存在依赖关系问题的点,那么每个子树一定选了一个包含根的连通块。

fi,jf_{i,j} 表示 dfs 序大于等于 ii 的点构成重量和为 jj 且不出现依赖关系问题的最大权值。

如果 ii 选,则后面只要不存在问题,加入 ii 后仍不出现问题,所以 fi,jfi+1,jwi+vif_{i,j}\leftarrow f_{i+1,j-w_i}+v_i

如果 ii 不选,则 ii 的子树也不选,所以 fi,jfi+szi,jf_{i,j}\leftarrow f_{i+sz_i,j}

答案即为 max0iW{f1,i}\max_{0\leq i\leq W}\{f_{1,i}\}

时间复杂度:O(NW)O(NW)

Code

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
#include <bits/stdc++.h>

// #define int int64_t

const int kMaxN = 5e4 + 5, kMaxT = 6.1e7 + 5;

int n, m;
int p[kMaxN], w[kMaxN], v[kMaxN], pool[kMaxT];
int dfn[kMaxN], sz[kMaxN], idx[kMaxN];
std::vector<int> G[kMaxN];

void dfs(int u) {
static int cnt = 0;
idx[dfn[u] = ++cnt] = u, sz[u] = 1;
for (auto v : G[u]) {
dfs(v);
sz[u] += sz[v];
}
}

void dickdreamer() {
std::cin >> n >> m;
memset(pool, 0xcf, sizeof(pool));
int (&f)[n + 2][m + 1] = decltype(f)(pool);
for (int i = 1; i <= n; ++i) {
std::cin >> p[i];
if (p[i]) G[p[i]].emplace_back(i);
}
for (int i = 1; i <= n; ++i) std::cin >> w[i];
for (int i = 1; i <= n; ++i) std::cin >> v[i];
for (int i = 1; i <= n; ++i)
if (!p[i])
dfs(i);
f[n + 1][0] = 0;
for (int i = n; i; --i) {
for (int j = m; ~j; --j) {
f[i][j] = f[i + sz[idx[i]]][j];
if (j >= w[idx[i]]) f[i][j] = std::max(f[i][j], f[i + 1][j - w[idx[i]]] + v[idx[i]]);
}
}
std::cout << *std::max_element(f[1], f[1] + 1 + m) << '\n';
}

int32_t main() {
#ifdef ORZXKR
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif
std::ios::sync_with_stdio(0), std::cin.tie(0), std::cout.tie(0);
int T = 1;
// std::cin >> T;
while (T--) dickdreamer();
// std::cerr << 1.0 * clock() / CLOCKS_PER_SEC << "s\n";
return 0;
}

LOJ #160. 树形背包 题解
https://sobaliuziao.github.io/2024/08/27/post/ec965a4a.html
作者
Egg_laying_master
发布于
2024年8月27日
许可协议