CodeForces 600E Lomsat gelral

题目大意

%   一棵树有 n n n 个结点,每个结点上有一种颜色,每个颜色有一个编号,求树中每个子树的最多的颜色编号的和。
  数据范围  1 ⩽ n ⩽ 1 0 5 1\leqslant n\leqslant 10^5 1n105

题解

%   考虑dsu on tree的过程。

  1. 递归处理轻儿子。
  2. 递归处理重儿子。
  3. 暴力统计除了重儿子之外的子树信息,加到桶中。
  4. 累加答案。
  5. 如果当前节点 u u u 不是 u u u 的父节点的重儿子,删除这颗子树(包括 u u u 的重儿子)对桶的所有贡献,否则保留信息在桶中,直接返回。

%   这个过程对于一个已经明白算法运行逻辑的人来说已经很清楚了,但是对不不明白的人来说,仍然比较混乱,而且似乎找不到什么好的措辞来解释这部分内容,因此最好的办法是结合代码使用,下面的代码尽量写得每一行关键代码都有注释。
  理解后我们来考虑这个过程的时间复杂度,对于一个点 u u u,若其到根节点的路径上有 k k k 条轻边,则这个点会被计算 k k k 次,由树链剖分的知识,我们可以得到,对于任意点 u u u,恒有 k ⩽ log ⁡ 2 n k\leqslant \log_2 n klog2n,因此总时间复杂度不超过 T ( n ) = Θ ( n log ⁡ 2 n ) \text T(n)=\Theta(n\log_2 n) T(n)=Θ(nlog2n)

#include<bits/stdc++.h>
using namespace std;
#define maxn 100010
struct edge{
	int v,next;
}edges[maxn<<1];
int n,head[maxn];
void ins(int u,int v){
	static int cnt=0;
	edges[++cnt]=(edge){v,head[u]};
	head[u]=cnt;
}
int size[maxn],son[maxn];
void dfs(int u,int fa=-1){
	size[u]=1;
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa) continue;
		dfs(v,u); size[u]+=size[v];
		if(size[son[u]]<size[v]) son[u]=v;
	}
}
int c[maxn];//节点的颜色
int cnt[maxn];//颜色的出现次数,即桶 
int max_cnt;//出现次数最多的颜色的出现次数 
int stop;//禁足点,用于标记重儿子 
long long sum;//出现次数最多的颜色的编号和 
void calc(int u,int fa,int val){
	cnt[c[u]]+=val;//累加到桶重 
	if(max_cnt<cnt[c[u]]) max_cnt=cnt[c[u]],sum=c[u];//找到了出现次数更多的颜色,更新答案 
	else if(max_cnt==cnt[c[u]]) sum+=c[u];//又出现了一种出现次数一样多的颜色,累加答案 
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa||v==stop) continue;//忽略禁足点和父节点 
		calc(v,u,val);//暴力递归统计答案 
	}
}
long long ans[maxn];
void work(int u,int fa,int chain){
	for(int i=head[u];i;i=edges[i].next){
		int v=edges[i].v;
		if(v==fa||v==son[u]) continue;//先忽略中儿子和父亲 
		work(v,u,0);//递归处理轻儿子 
	}
	if(son[u]){//考虑重儿子 
		work(son[u],u,1);//递归处理重儿子 
		stop=son[u];//标记重儿子不能在下面的calc函数中计算,因为在上一行的递归中,没有删除重儿子桶的贡献,已经被统计过了。 
	}
	calc(u,fa,1);//暴力统计所有轻儿子的答案 
	stop=0;//这里删除对重儿子的禁足是为了下面50行的calc函数能把包括重儿子的整颗子树都删掉。 
	ans[u]=sum;//存下这个节点的答案——颜色编号和。 
	if(chain==0){//如果这是一条重链的开头,意味着其父亲和它的连边为轻边,要删除贡献 
		calc(u,fa,-1);//删除整颗子树的贡献 
		sum=max_cnt=0;//清空统计的信息 
	}
}
int main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&c[i]);
	for(int i=1,u,v;i<n;i++){
		scanf("%d%d",&u,&v);
		ins(u,v);ins(v,u);
	} dfs(1); work(1,-1,1);
	for(int i=1;i<=n;i++)
		printf("%I64d ",ans[i]);
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值