CF#600E Lomsat gelral---DSU on tree

(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;
}
posted @ 2019-10-06 13:31  zxynothing  阅读(105)  评论(0)    收藏  举报