CF600E Lomsat gelral dsu on tree

人有多大胆,地有多大产:(

先放题面

大意:给出一棵树,每个节点有颜色,求每个子树内的出现次数最多的颜色,如果多个颜色的出现次数相同,则将它们的颜色编号加起来。

思路:重链剖分大家都会吧,我们对这棵树进行轻重链剖分后,对于一个节点,先暴力统计轻儿子的答案,再删除轻儿子的所有贡献,然后统计重儿子的答案,不删除重儿子的贡献。最后对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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值