【题解】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;
}

浙公网安备 33010602011771号