人有多大胆,地有多大产:(
大意:给出一棵树,每个节点有颜色,求每个子树内的出现次数最多的颜色,如果多个颜色的出现次数相同,则将它们的颜色编号加起来。
思路:重链剖分大家都会吧,我们对这棵树进行轻重链剖分后,对于一个节点,先暴力统计轻儿子的答案,再删除轻儿子的所有贡献,然后统计重儿子的答案,不删除重儿子的贡献。最后对x的子树的轻儿子再遍历一次计算贡献。
淦,这不就是暴力吗,凭什么能过啊。
但是,让我们冷静分析。
考虑对于每一个节点x,它被遍历到的情况无非2种:
- 作为重儿子被遍历
- 它的一个祖先跳到轻儿子,且这个轻儿子也是x的祖先。
对于第一种,显然只会被遍历1次
还记得轻重链的性质吗?一个节点最多跳
l
o
g
n
logn
logn次轻边就能到达根节点,那么每个节点被遍历的次数就不超过
l
o
g
n
logn
logn次,所以时间复杂度是
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)!比莫队不知道高到哪里去了
当然,它是有它的局限的:
- 不能带修改
- 只能查询子树信息
如果发现有这种性质,那恭喜你,你发现了一个又好写又好调常数还小的做法!
code:
#include<bits/stdc++.h>
using namespace std;
#define LL long long
inline int read(){
int a=0;char c=getchar();
while(c>'9'||c<'0')c=getchar();
while('0'<=c&&c<='9'){
a=a*10+c-48;
c=getchar();
}
return a;
}
#define MN 100005
int siz[MN],fa[MN],w[MN],col[MN],vis[MN],n,u,v,MAX;
LL ans[MN],sum;
vector<int>edge[MN];
void dfs1(int x){
siz[x]=1;
for(int i=0;i<edge[x].size();++i)
if(edge[x][i]!=fa[x]){
fa[edge[x][i]]=x;
dfs1(edge[x][i]);
siz[x]+=siz[edge[x][i]];
if(siz[w[x]]<siz[edge[x][i]])w[x]=edge[x][i];
}
}//轻重链剖分
void calc(int x,int op,int W){
vis[col[x]]+=op;
if(vis[col[x]]>MAX) MAX=vis[col[x]],sum=col[x];
else if(vis[col[x]]==MAX) sum+=col[x];
//暴力统计
for(int i=0;i<edge[x].size();++i)
if(edge[x][i]!=fa[x]&&edge[x][i]!=W)
calc(edge[x][i],op,W);
//W的影响没有被消去,所以不用遍历
}
void dfs2(int x,int op){
for(int i=0;i<edge[x].size();++i){
if(edge[x][i]!=fa[x]&&edge[x][i]!=w[x])
dfs2(edge[x][i],0);//0表示删除影响,1表示不删
}
if(w[x]) dfs2(w[x],1);
calc(x,1,w[x]);//w影响未删,所以计算时不用计算
ans[x]=sum;//x的答案
if(!op){
calc(x,-1,0);//注意,如果整个x的子树的贡献都要删除,那么它的重儿子也要删除
sum=0;
MAX=0;
}
}
int main(){
n=read();
for(int i=1;i<=n;++i)col[i]=read();
for(int i=1;i<n;++i){
int u=read(),v=read();
edge[u].push_back(v);
edge[v].push_back(u);
// vector txdy!
}
dfs1(1);
dfs2(1,1);
for(int i=1;i<=n;++i)
printf("%I64d\n",ans[i]);
return 0;
}