YellowBean_Elsa
2019-10-14 16:44:47
这道题我们可以遍历整棵树,并用一个数组ap(appear)记录每种颜色出现几次
但是每做完一棵子树就需要清空ap,以免对其兄弟造成影响。
而这样做它的祖先时就要把它重新搜一遍,浪费时间
但是我们发现,对于每个节点v,最后一棵子树是不用清空的,因为做完那棵子树后可 以把其结果直接加入v的答案中。
选哪棵子树呢?当然是所含节点最多的一棵咯,我们称之为“重儿子”
其实感觉这样快不了多少……但是它竟然是nlogn的!
先用一遍dfs算出每个点是否为重儿子
再dfs统计答案,每次碰到重儿子就跳过,递归完清空ap数组等东东
最后dfs重儿子,不清空
再对当前节点进行另一种dfs,暴力统计ap,不做重儿子
看代码吧~~~(这是帅的人最不关心的部分)
------------我是分割线
//written by YellowBean, the AKer of IMO (rubbish)
#include<bits/stdc++.h>
#define ll long long
#define re register
using namespace std;
const int N=2e5+10;
int n;
int c[N];//color
int v[N],nex[N],first[N],tot=1;
inline void add(int x,int y){
v[++tot]=y;
nex[tot]=first[x];
first[x]=tot;
}
inline int read(){
int x=0;char ch=getchar();
while(!isdigit(ch))ch=getchar();
while(isdigit(ch))x=(x<<1)+(x<<3)+ch-'0',ch=getchar();
return x;
}
ll ans[N],ap[N],mx,sum;//十年OI一场空,不开 long long 见祖宗
//ap表示每种颜色出现几次 mx表示出现最多的次数 sum表示颜色编号和
int sz[N];//子树大小
bool gson[N];//表示一个点是否为重儿子
void getg(int x,int f){//get 子树大小 以及 重儿子
sz[x]=1;
int mx=0,p=0;
for(re int i=first[x];i;i=nex[i]){
int y=v[i];
if(y==f)continue;
getg(y,x);sz[x]+=sz[y];
if(sz[y]>mx){
mx=sz[y];
p=y;
}
}if(p)gson[p]=1;
}
void DFS(int x,int f,int p){//暴力遍历子树 p为重儿子 之后需init清空
//统计答案
ap[c[x]]++;
if(ap[c[x]]>mx){
mx=ap[c[x]];
sum=c[x];
}else if(ap[c[x]]==mx)sum+=c[x];
for(re int i=first[x];i;i=nex[i]){
int y=v[i];
if(y==f || y==p)continue;//不要把重儿子也一起遍历了!
DFS(y,x,p);
}
}
inline void init(int x,int f){//暴力遍历后清空
ap[c[x]]--;
for(re int i=first[x];i;i=nex[i]){
int y=v[i];
if(y==f)continue;
init(y,x);
}
}
void dfs(int x,int f){//启发式合并关键函数!
int p=0;//重儿子标记
for(re int i=first[x];i;i=nex[i]){
int y=v[i];
if(y==f)continue;
if(!gson[y]){//不是重儿子的暴力做
dfs(y,x);
init(y,x);
sum=mx=0;
}
else p=y;
}if(p)dfs(p,x);//重儿子单独特判
DFS(x,f,p);
ans[x]=sum;
}
int main(){
n=read();
for(re int i=1;i<=n;i++)c[i]=read();
for(re int i=1;i<n;i++){
int x=read(),y=read();
add(x,y),add(y,x);
}getg(1,0);
dfs(1,0);
for(re int i=1;i<=n;i++)
printf("%lld ",ans[i]);
}
//those who read but don't copy are handsome
关于复杂度证明:
对于每个节点,它被计算的次数就是它到根节点路径上的轻边(连到轻儿子的边)数
我们只需算出轻边有几条
由于每从一个深度为d的节点沿一条轻边走到深度(d-1)的节点,子树大小就至少*2 (这是因为有一个重儿子>=当前子树)
所以最多只有logn条轻边!
故复杂度为O(nlogn)!