原题连接
tag: 树形dp, 贪心, *2100

题意

给定一棵大小为 的树,每个节点都有一个海狸,每次去到一个结点必须取走一个海狸,如果目的节点的值为0则无法前往,最多能够取走多少个海狸。

题解

首先可以发现每个子树如果能够最优显然问题就一定是最优解,所以联想到树形 dp。设 为从 出发,在其子树中的最优解。

考虑状态转移,父节点的 dp 值显然是由子树的贡献加上到达子树所产生的贡献(因为每次都要从父节点出发最后回到父节点)。同时,由于父节点海狸数的限制,只能访问一定数量的子树,因此可以贪心的选取贡献大的子树进行访问。

如果父节点访问玩所有子树还有剩余的海狸,则可以继续和儿子中的剩余进行来回访问,将海狸数耗尽为止。

由于每次前进到相邻的边必须保证目的节点有海狸,因此除了根节点之外的所有点,考虑它们的子树的一个前提是必须能够从根节点走过来,也就是必须要花费一个海狸,因此要对非根节点的所有节点的海狸数量进行减一操作。

时间复杂度

代码

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

using ll = long long;
using namespace std;

int main()
{
int n;
cin >> n;
vector<ll> a(n + 1);
vector<vector<int>> G(n + 1);
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
G[u].push_back(v), G[v].push_back(u);
}
int rt;
cin >> rt;
vector<ll> dp(n + 1);

auto dfs = [&](auto dfs, int u, int fa) -> void {
if (u != rt) a[u]--;
vector<int> tmp;
for (int v : G[u]) {
if (v == fa) continue;
dfs(dfs, v, u);
tmp.push_back(v);
}
sort(tmp.begin(), tmp.end(), [&](int a, int b) { return dp[a] > dp[b]; });
for (int i = 0; i < tmp.size() && a[u]; i++) {
dp[u] += dp[tmp[i]] + 2;
a[u]--;
}
if (a[u]) {
for (int v : tmp) {
if (a[v]) {
int t = min(a[v], a[u]);
dp[u] += 2 * t;
a[u] -= t;
}
}
}
};

dfs(dfs, rt, 0);

cout << dp[rt] << endl;
return 0;

}