「CEOI2019」魔法树

Review - Dsu On Tree

#3166.「CEOI2019」魔法树

Summarize

给定 $n$ 个节点的树,每个时间点 $d$ 可以砍下一些边,可以收获得所有与根不连通且 $d_i=d$ 的点的权值 $w_i$ ,求获得的最大权值和。

$n \le 100000$

Solution

这是一道非常漂亮的题!

定义状态: $f[u][i]$ 表示在第 $i$ 天砍掉 $u->fa[u]$ 的一条边,所能获得的最多果汁数量。

状态转移方程:
$$
f[u][i]=\sum_{v\in son(u)}{(\max_{j=1}^{i}f[v][j])}
$$
解法一:用动态开点权值线段树维护 $f[u]$,转移时在线段树上操作即可。码量较大,常数较大。

解法二:不难发现 $f[u][1]$ 到 $f[u][k]$ 是单调不下降的,因此设 $g[u][i]=\max_{j=1}^i f[u][j]$。用差分数组维护 $g[u]$,观察到差分数组中非零元素个数不超过 $size[u]$,可以用集合(stl::map) 记录差分数组中的所有非零元素。

在集合合并时,采取 dsu on tree,复杂度 $O(n\log^2 n)$。本题的关键点在于如果当前位置有果实,那么还需要对差分数组进行一些修改:将差分数组的 $d[u]$ 位置加上 $w[u]$,在 $d[u]$ 之后的部分则需依次减掉此次操作的值。

码量较小,常数较小。

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
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

const int maxn = 100005;

vector<int> G[maxn];
map<int, ll> mp[maxn];
int d[maxn];
ll w[maxn];

int N, M, K;

void dfs(int u) {
for (auto v: G[u]) {
dfs(v);
if (mp[u].size() < mp[v].size()) swap(mp[u], mp[v]);
for (auto it: mp[v]) {
mp[u][it.first] += it.second;
}
}
if (d[u]) {
mp[u][d[u]] += w[u];
for (auto it = mp[u].upper_bound(d[u]); it != mp[u].end();) {
if (it->second > w[u]) {
it->second -= w[u];
break;
}
w[u] -= it->second;
it->second = 0;
mp[u].erase(it++);
}
}
}

int main() {
scanf("%d%d%d", &N, &M, &K);
for (register int i = 2; i <= N; ++i) {
int p;
scanf("%d", &p);
G[p].push_back(i);
}
for (register int i = 1; i <= M; ++i) {
int v;
scanf("%d", &v);
scanf("%d%I64d", &d[v], &w[v]);
}
dfs(1);
ll ans = 0;
for (auto it: mp[1]) ans += it.second;
printf("%I64d", ans);
return 0;
}

Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×