【CCPC2021网络选拔赛(重赛)】HDU-7136 Jumping Monkey 题解


题目大意

给定一个有 $n$ 个节点的树,每个节点有一个权值 $val[i]$,一个猴子在每个节点之间进行移动,假如它在节点 $u$ 上,如果存在节点 $v$,$val[v]$ 是从 $u$ 到 $v$ 的路径上权值最大的点,那么它就可以从 $u$ 移动到 $v$ 上。问:猴子分别从 $1$ 到 $n$ 号节点出发,分别最多能移动到多少的节点上?

思路

我们先观察一下,可以得出以下结论:

  1. 在一个节点 $u$ 上移动的最优策略是,移动到它能移动到的最小的节点 $v$ 上,用 $ans$ 记录每个点的答案的话,$ans[u] = ans[v] + 1$。也就是说,此时,节点 $u$ 与哪些点相连已经不重要了,它的答案只与 $v$ 有关。
  2. 每个点移动的终点都是整个树中权值最大的点,假设为 $s$,如果把 $s$ 删去,树变得不连通了,分成了几个小的连通块,就意味着不同连通块之间是无法相互抵达的,因为它们都需要经过权值最大的点 $s$。

    如果我们在删的过程中,更新一下 $ans$,删掉 $s$ 时,令 $ans[s] = 1$。

    之后在剩下的连通块中,也做同样的操作,假设其中一个连通块中权值最大的点为 $x$,删去后,令 $ans[x] = ans[s] + 1$。

    因为每个连通块中节点的终点也是此连通块中权值最大的点,而到了这个点之后,就只能去节点 $s$ 了。这么递归做下来,$ans[i]$ 就是节点 $i$ 的答案了。

但是第二个结论很难直接去做。而第一个结论中移动到它能移动到的最小的节点 $v$ 上,这个 $v$ 的寻找也很困难。但是我们可以借用结论2的思路,去寻找 $v$。新建一棵树,把 $u$ 与 $v$ 相连,形成一棵新的树,以权值最大的节点为根,那么每个节点的深度就是这个点的答案。

例子

假设原有的图是(数字代表权值):3 - 1 - 2 - 5 - 4

新的树:1 - 2 - 3 - 5 - 4。

具体做法

我们把结论2的做法逆过来,即从权值最小的点开始加入,依次加入权值递增的节点,形成不同的连通块。

每当我们加入一个节点 $u$ 时,扫描它在原有的图的出边,检测是否有已经加入的节点,如果有节点(这个节点所在的连通块中的所有点权值一定都是小于 $u$ 的),设为 $v$,说明 $v$ 所在的连通块可以以 $u$ 作为终点。所以,把 $u$ 与 $v$ 所在的连通块中,权值最大的点在新的图中连一条边。

我们利用并查集来记录连通块,因为我们是按照递增顺序遍历节点的,所以可以很容易做到每个连通块的并查集父亲为当前连通块中权值最大的点

代码

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;

const int maxN =1e5 + 7;

int T, n, fa[maxN], depth[maxN];
vector<int> e1[maxN], e2[maxN];

struct Node {
int val, num;
}a[maxN];

int Find(int x)
{
return fa[x] == x? x : fa[x] = Find(fa[x]);
}

bool cmp(Node x, Node y)
{
return x.val < y.val;
}

inline void init()
{
for(int i = 1; i <= n; ++i) {
fa[i] = depth[i] = 0;
e1[i].clear();
e2[i].clear();
}
}

void dfs(int now)
{
for(int i = 0; i < e2[now].size(); ++i) {
int y = e2[now][i];
if(depth[y])
continue;
depth[y] += depth[now] + 1;
dfs(y);
}
}

inline void work()
{
scanf("%d", &n);
init();
for(int i = 1; i < n; ++i) {
int x, y; scanf("%d%d", &x, &y);
e1[x].push_back(y);
e1[y].push_back(x);
}
for(int i = 1; i <= n; ++i) {
scanf("%d", &a[i].val);
a[i].num = i;
}
sort(a + 1, a + 1 + n, cmp);
for(int i = 1; i <= n; ++i) {
fa[a[i].num] = a[i].num;
for(int j = 0; j < e1[a[i].num].size(); ++j) {
if(fa[e1[a[i].num][j]]) {
int f = Find(e1[a[i].num][j]);
fa[f] = a[i].num;
e2[f].push_back(a[i].num);
e2[a[i].num].push_back(f);
}
}
}
depth[a[n].num] = 1;
dfs(a[n].num);
for(int i = 1; i <= n; ++i)
printf("%d\n", depth[i]);
}

int main()
{
scanf("%d", &T);
while(T--) {
work();
}
return 0;
}

Author: BY 水蓝
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source BY 水蓝 !
  TOC