题解 - CF600E Lomsat gelral
树形DSU模板题 | 难度:提高+/省选
[题目链接] Lomsat gelral [原题链接] E. Lomsat gelral
Lomsat gelral
题面翻译
有一棵 $n$ 个结点的以 $1$ 号结点为根的有根树。 每个结点都有一个颜色,颜色是以编号表示的, $i$ 号结点的颜色编号为 $c_i$。 如果一种颜色在以 $x$ 为根的子树内出现次数最多,称其在以 $x$ 为根的子树中占主导地位。显然,同一子树中可能有多种颜色占主导地位。 你的任务是对于每一个 $i\in[1,n]$,求出以 $i$ 为根的子树中,占主导地位的颜色的编号和。 $n\le 10^5,c_i\le n$
输入格式
The first line contains integer $n$ ( $1<=n<=10^{5}$ ) — the number of vertices in the tree.
The second line contains $n$ integers $c_{i}$ ( $1<=c_{i}<=n$ ), $c_{i}$ — the colour of the $i$ -th vertex.
Each of the next $n-1$ lines contains two integers $x_{j},y_{j}$ ( $1<=x_{j},y_{j}<=n$ ) — the edge of the tree. The first vertex is the root of the tree.
输出格式
Print $n$ integers — the sums of dominating colours for each vertex.
样例 #1
样例输入 #1
1
2
3
4
5
4
1 2 3 4
1 2
2 3
2 4
样例输出 #1
1
10 9 3 4
样例 #2
样例输入 #2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
15
1 2 3 1 2 3 3 1 1 3 2 2 1 2 3
1 2
1 3
1 4
1 14
1 15
2 5
2 6
2 7
3 8
3 9
3 10
4 11
4 12
4 13
样例输出 #2
1
6 5 4 3 2 3 3 1 1 3 2 2 1 2 3
题解
不删除这个输入输出格式是有原因的
解法不少,看到过线段树合并、莫队的算法,不过最近在学 $dsu$,那我就写 $dsu$。
DSU ON TREE
中文名树上启发式合并,在笔记中有提到。
思想就是:dfs的时候先去解决那些比较小的儿子,先加上再退回,最后处理最大的儿子时就不用再退回,从而节省时间复杂度。
时间复杂度证明:
我们称当前节点最大的儿子称为重儿子,其他儿子称为轻儿子。 每个节点走到根节点经过的轻边的数量就是这个节点被统计的数量。如果该边为轻边,那一定有一个重边,且重儿子的数量大于等于当前轻儿子的重量,也就是说,每往上经过一条轻边,节点数量必然翻倍,也就是说每个节点最多会被统计 $log_n$ 遍,总复杂度就为 $\mathcal{O}(nlogn)$。
这道题的做法: 维护一个颜色数组 $col$ ,统计当前情况各种颜色的数量,每遍历完一个轻节点,就清零。最后遍历重节点,遍历完把轻节点加上,统计答案。重节点自始至终不清零!
注意:题目给出的是边而不是父子关系,所以建双向边!
AC 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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
#include<bits/stdc++.h>
//几乎就是模板题了
using namespace std;
typedef long long ll;
const int N = 2e5 + 10;
int n;
int c[N];
ll ans[N];
int maxs[N];
int cnt[N];
vector<int> son[N];
ll col[N], mx, sum;
//如何传递mx
void Dfs(int pos, int fa, int p) {//统计答案
col[c[pos]]++;
if(col[c[pos]] > mx) mx = col[c[pos]], sum = c[pos];
else if(col[c[pos]] == mx) sum += c[pos];
for(auto i : son[pos]) {
if(i == fa) continue;
if(i != p) Dfs(i, pos, p);
}
}
void init(int pos, int fa) {//参考题解写的递归清空,将某颗子树的col全部清空
col[c[pos]]--;
for(auto i : son[pos]) {
if(i == fa) continue;
init(i, pos);
}
}
void dfs(int pos, int fa) {
int p = 0;
for(auto i : son[pos]) {
if(i == fa) continue;
if(i != maxs[pos]) {
//如果不是大儿子,从小儿子开始搜
dfs(i, pos);
init(i, pos);
sum = 0, mx = 0;
//最后计算自己的时候,要再dfs一遍是吧
} else p = i;
}
if(p) dfs(p, pos);
Dfs(pos, fa, p);//统计答案,并加上所有儿子
//最后的状态是要全部加上的,以便回溯,如果这个子树不被需要,会清空
ans[pos] = sum;
}
void get_v(int pos, int fa) {
for(auto i : son[pos]) {
if(i == fa) continue;
get_v(i, pos);
cnt[pos] += cnt[i];
if(cnt[i] > cnt[maxs[pos]]) maxs[pos] = i;
}
}
int main()
{
// freopen(".in", "r", stdin);
// freopen(".out", "w", stdout);
scanf("%d", &n);
for(int i = 1; i <= n; i++) {
scanf("%d", &c[i]);
cnt[i] = 1;
}
int u, v;
for(int i = 1; i < n; i++) {
scanf("%d%d", &u, &v);
son[u].emplace_back(v);
son[v].emplace_back(u);
}
get_v(1, 0);
dfs(1, 0);
for(int i = 1; i <= n; i++) {
printf("%lld ", ans[i]);
}
return 0;
}