题目大意:给你1号点为根节点的树,树上每个点有一个颜色值,定义一个结点的支配色:该结点的为根的子树内出现次数最多的颜色。一个结点的支配色可能有好几个,求每个节点的支配色的出现次数之和。
算法标签:dsu on tree
题解:树上启发式合并裸题。如果暴力统计答案 需要
O
(
n
2
)
O(n ^ 2)
O(n2)来统计每个结点的答案。考虑用空间换时间,对每个颜色开一个桶,父节点的答案由子结点的答案合并得到,时间可以降下来但空间复杂度达到了
O
(
n
2
)
O(n ^ 2)
O(n2)。
看树上启发式合并是如何做的:既然不能将每个点的信息都向上传,考虑只将重儿子的信息向上传,重儿子和轻儿子和树链剖分的概念相同。做法是:对于当前处理结点,先递归处理其轻儿子,清除轻儿子维护的信息,然后递归处理重儿子,保留重儿子的信息,再暴力合并轻儿子树内的信息。
复杂度O(
n
log
n
n\log n
nlogn)
(还可以将树转dfs序,在dfs序上跑莫队,复杂度 O ( n n ) O(n\sqrt n) O(nn))
#include<bits/stdc++.h>
using namespace std;
#define fir first
#define sec second
typedef long long ll;
const int maxn = 4e5 + 10;
int n,c[maxn];
vector<int> g[maxn];
int cnt[maxn],son[maxn],f[maxn],vis[maxn],mxval;
int head[maxn],nxt[maxn],to[maxn],sz;
ll cur,ans[maxn];
void init() {
fill(head,head + n + 1,-1);
sz = 0;
}
void add(int u,int v) {
to[sz] = v;
nxt[sz] = head[u];
head[u] = sz++;
}
void dfs1(int u,int fa) {
son[u] = 0;
f[u] = 1;
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == fa) continue;
dfs1(to[i],u);
f[u] += f[to[i]];
if(!son[u] || f[son[u]] < f[to[i]]) son[u] = to[i];
}
}
void modify(int u,int fa,ll &cur,int v) {
cnt[c[u]] += v;
if(v > 0 && cnt[c[u]] >= mxval) {
if(cnt[c[u]] > mxval) cur = 0,mxval = cnt[c[u]];
cur += c[u];
}
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == fa || vis[to[i]]) continue;
modify(to[i],u,cur,v);
}
}
void dfs2(int u,int fa,int keep) {
for(int i = head[u]; i + 1; i = nxt[i]) {
if(to[i] == son[u] || to[i] == fa) continue;
dfs2(to[i],u,0);
}
if(son[u]) {
dfs2(son[u],u,1);vis[son[u]] = 1;
}
modify(u,fa,cur,1);
if(son[u]) vis[son[u]] = 0;
ans[u] = cur;
if(keep == 0) {
modify(u,fa,cur,-1);
cur = 0;
mxval = 0;
}
}
int main() {
scanf("%d",&n);
init();
for(int i = 1; i <= n; i++)
scanf("%d",&c[i]);
fill(ans,ans + n + 1,0);
for(int i = 1; i < n; i++) {
int u,v;scanf("%d%d",&u,&v);
add(u,v);add(v,u);
}
dfs1(1,-1);
dfs2(1,-1,0);
for(int i = 1; i <= n; i++)
if(i == 1) printf("%lld",ans[i]);
else printf(" %lld",ans[i]);
return 0;
}