【题解】CF600E Lomsat gelral

CF600E】题解

一:【题目描述】

  • 每个点都带有点权,一颗以u为根的子树的价值定义为 子树内出现次数最多的若干点权的和
  • 求对于每一颗子树u(1<=u<=n),输出其价值

二:【分析】

如果我们对于每一颗子树暴力计算,复杂度O(n^2),显然不可以接受
考虑优化,对于一颗子树tree,根节点为u
首先暴力计算 以u的轻儿子为根的若干子树的答案,然后清空统计数组,最后计算 以重儿子为根的子树的答案,统计数组不做清空
我们可以对重儿子的统计数组,暴力拓展轻儿子,得到了tree的统计数组,从而得到答案
这种优化算法被称作dsu on tree(树上启发式合并)

三:【时间复杂度】

1.【暴力算法】

  • 一个节点向上跳,有多少条链,就会被计算几次,复杂的O(n^2)

2.【dsu on tree】

  • 一个节点向上跳,如果这条链是重链,则会直接拓展其他节点,不会被重新计算;如果这条链是轻链,则会被计算一次
  • 一个节点向上跳,会遇到的轻链个数为logn级别,所以总复杂度为O(nlogn)

四:【代码】

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=1e5+10;
int nw[N];
vector<int> mp[N];
int siz[N],son[N];
void Son(int u,int fa){
	siz[u]=1;
	for(auto v:mp[u]){
		if(v==fa) continue;
		Son(v,u);
		siz[u]+=siz[v];
		if(son[u]==0||siz[v]>siz[son[u]]) son[u]=v;
	}
}
int num[N];
LL as[N];
void clear(int u,int fa){
	num[nw[u]]--;
	for(auto v:mp[u]){
		if(v==fa) continue;
		clear(v,u);
	}
}
LL ans=0,maxn=0;
void extra(int u,int fa,int no){
	num[nw[u]]++;
	if(num[nw[u]]==maxn) ans+=nw[u];
	else if(num[nw[u]]>maxn){
		maxn=num[nw[u]];
		ans=nw[u];
	}	
	for(auto v:mp[u]){
		if(v==fa||v==no) continue;
		extra(v,u,no);
	}
}
void solve(int u,int fa){
	for(auto v:mp[u]){
		if(v==fa||v==son[u]) continue;
		solve(v,u);
		clear(v,u);
		ans=maxn=0;
	}
	//cout<<u<<" "<<son[u]<<"\n";
	if(son[u]) solve(son[u],u);
	extra(u,fa,son[u]);
	as[u]=ans;
}
int main(){
	//freopen("in.txt","r",stdin);
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int n;cin>>n;
	for(int i=1;i<=n;i++) cin>>nw[i];
	for(int i=1;i<n;i++){
		int a,b;cin>>a>>b;
		mp[a].push_back(b);
		mp[b].push_back(a);
	}
	Son(1,1);
	solve(1,1);
	for(int i=1;i<=n;i++) cout<<as[i]<<" ";
	return 0;
}
posted @ 2025-12-06 14:53  Ming3398  阅读(2)  评论(0)    收藏  举报