CF600E Lomsat gelral

题意

给你一棵有\(n\)个点的树 $ n \leq 10^5$,树上每个节点都有一种颜色 \(a_i\),让你求每个点子树出现最多的颜色的编号(多个就求和)

思路

考虑暴力怎么写:遍历每个节点—把子树中的所有颜色暴力统计出来算答案—清空—继续递归

这肯定是\(O(n^2)\)的。

\(dsu \space on \space tree\)巧妙的利用了轻重链的性质,把复杂度降到了\(O(nlogn)\)

\(dsu \space on \space tree\)的算法流程是这样的:

对于节点\(i\)

  • 遍历每一个节点
  • 递归解决所有的轻儿子,同时消除递归产生的影响
  • 递归重儿子,不消除递归的影响
  • 统计所有轻儿子对答案的影响
  • 更新该节点的答案
  • 删除所有轻儿子对答案的影响

这样子重儿子的子树就避免了重复操作
至于复杂度的证明,就算了吧

#include <bits/stdc++.h>
const int N=100005;
int edge,to[N<<1],last[N],Next[N<<1],size[N],son[N],Mx,cnt[N],a[N],n,x,y,Son; 
long long Ans,ans[N];
void add(int x,int y){
	to[++edge]=y;
	Next[edge]=last[x];
	last[x]=edge;
}
void dfs(int x,int fa){
	size[x]=1;
	for (int i=last[x];i;i=Next[i])
	if (to[i]!=fa){
		dfs(to[i],x);
		if (size[to[i]]>size[son[x]]) son[x]=to[i];
		size[x]+=size[to[i]];
	}
}
void add(int x,int fa,int opt){
	cnt[a[x]]+=opt;
	if (cnt[a[x]]>Mx) Mx=cnt[a[x]],Ans=a[x];
	else if (cnt[a[x]]==Mx) Ans+=a[x];
	for (int i=last[x];i;i=Next[i])
	if (to[i]!=fa && to[i]!=Son)
		add(to[i],x,opt);
}
void dfs2(int x,int fa,int opt){
	for (int i=last[x];i;i=Next[i])
	if (to[i]!=fa){
		if (to[i]!=son[x]) dfs2(to[i],x,0);
	}
	if (son[x]) dfs2(son[x],x,1),Son=son[x];
	add(x,fa,1);
	Son=0;
	ans[x]=Ans;
	if (!opt) add(x,fa,-1),Mx=0,Ans=0;
}
int main(){
	scanf("%d",&n);
	for (int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	for (int i=1;i<n;i++){
		scanf("%d%d",&x,&y);
		add(x,y),add(y,x);
	}
	dfs(1,0);
	dfs2(1,0,1);
	for (int i=1;i<=n;i++) printf("%I64d ",ans[i]);
}

注意

编号和会爆\(int\)

posted @ 2019-06-02 20:34  flyfeather  阅读(133)  评论(0编辑  收藏  举报