(https://codeforces.com/contest/600/problem/E)
题目描述
给一棵以\(1\)为根树,每个节点有一个颜色\(c_i\),求每个点的子树中颜色出现次数最多的颜色编号和。
\(1\leq n\leq 10^5,1\leq c_i\leq n\)
sol:
\(DSU\)就是这样一个处理子树内某些问题的工具,但不支持修改操作。首先需要重链剖分,接下来每次只要把轻儿子的贡献暴力往重儿子里并即可。但如果仔细想想就会发现,其实算重了轻儿子的贡献,于是一定要记住在算完一个节点的答案后要把轻儿子的影响去掉。
但这个时间复杂度为什么是对的?
首先考虑一个结论,从任一点到根的路径上轻边条数不会超过\(\log n\)条,因为显然轻儿子的大小不会超过整个子树的一半。利用这个结论,在极端情况,也就是一条路径上轻边条数恰为\(\log n\),那在每次统计轻边时都会遍历轻子树,故复杂度为\(O(\log n)\)再乘上遍历的复杂度。下面考虑遍历,每个节点只被访问一次,要么是它的祖先节点暴力统计轻儿子或消除影响时,要么是它自己统计答案时,复杂度\(O(n)\)。故总复杂度\(O(n\log n)\)。(带口胡成分)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline int read(){
int x=0;char c=getchar();
while(c<'0'||c>'9') c=getchar();
while(c>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x;
}
const int N=100005;
int n,p[N],ban,h[N],maxn;
ll sum,ans[N];
int ver[N<<1],nxt[N<<1],head[N],tot;
int fa[N],son[N],siz[N];
inline void link(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
void dfs1(int x,int la){
fa[x]=la;siz[x]=1;
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==la) continue;
dfs1(y,x);
siz[x]+=siz[y];
if(siz[y]>siz[son[x]]) son[x]=y;
}
}
void update(int x,int val){
h[p[x]]+=val;
if(h[p[x]]>maxn) maxn=h[p[x]],sum=p[x];
else if(h[p[x]]==maxn) sum+=p[x];
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa[x]||y==ban) continue;
update(y,val);//每次都需要在整棵子树内删除某个点的贡献
}
}
void dfs2(int x,int opt){
for(int i=head[x];i;i=nxt[i]){
int y=ver[i];
if(y==fa[x]||y==son[x]) continue;
dfs2(y,0);//暴力统计轻边的贡献,opt = 0表示递归完成后消除对该点的影响
}
if(son[x]) dfs2(son[x],1);//统计重儿子的贡献,不消除影响
ban=son[x];update(x,1);//暴力统计所有轻儿子的贡献
ans[x]=sum;ban=0;
if(!opt) update(x,-1),maxn=sum=0;//如果需要删除贡献的话就删掉
}
int main(){
n=read();
for(int i=1;i<=n;i++) p[i]=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
link(x,y);link(y,x);
}
dfs1(1,0);dfs2(1,1);
for(int i=1;i<=n;i++)
printf("%I64d ",ans[i]);
return 0;
}