CFP600E Lomsat gelral 学习笔记
CFP600E Lomsat gelral 学习笔记
题意简述
给定一棵 \(n\) 个结点,以 \(1\) 为根的树。每个结点 \(u\) 都有一个颜色 \(c_u\)。称一种颜色 \(x\) 在 \(u\) 的子树中占主导地位,当且仅当不存在其它颜色在子树中出现次数大于 \(x\)。
对于每一个 \(i\in [1,n]\),求出以 \(i\) 为根的子树中,占主导地位的颜色的编号和。
\(1\le c_u\le n\le 10^5\)。
做法解析
由于我们要讲的是启发式合并,所以我们暂时不带脑子,暴力,干他!一个大法师下去全干了。暴力的做法就是遍历每一个结点,对于每一个结点将它子树内的颜色统计一下。
但是你考虑这样的情景:你在遍历点 \(u\) 的每一个儿子的时候,都得把上个儿子的颜色统计数组清空。也就是说每个点 \(i\) 的颜色都会被数 \(dep_i\) 次(每个祖先都会数到它一遍)。所以说这个是 \(n^2\) 的,过不了。
然而我们发现:当我们数到最后一个儿子的时候,它的贡献可以直接继承给它父亲。所以我们用重链剖分的思想,我们让重儿子成为最后一个被遍历的儿子,将贡献继承上去。
这么做就是 \(O(n\log n)\) 了!因为每个结点只会被它往上直链中长链数次地重新计算。这就是我们的树上启发式合并。
代码实现
#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
const int MaxN=1e5+5;
int N,C[MaxN],X,Y;
vector<int> Tr[MaxN];
void addudge(int u,int v){
Tr[u].push_back(v);
Tr[v].push_back(u);
}
int siz[MaxN],hson[MaxN];
void dfs1(int u,int f){
siz[u]=1;
for(int v : Tr[u]){
if(v==f)continue;
dfs1(v,u),siz[u]+=siz[v];
if(siz[v]>=siz[hson[u]])hson[u]=v;
}
}
int apr[MaxN],cmx;lolo cans,ans[MaxN];
void clesr(int u,int f){
apr[C[u]]--;
for(int v : Tr[u])if(v!=f)clesr(v,u);
}
void dfs3(int u,int f,int h){
apr[C[u]]++;
if(apr[C[u]]>cmx)cmx=apr[C[u]],cans=C[u];
else if(apr[C[u]]==cmx)cans+=C[u];
for(int v : Tr[u])if(v!=f&&v!=h)dfs3(v,u,h);
}
void dfs2(int u,int f){
for(int v : Tr[u]){
if(v==f||v==hson[u])continue;
dfs2(v,u),clesr(v,u),cans=cmx=0;
}
if(hson[u])dfs2(hson[u],u);
dfs3(u,f,hson[u]);
ans[u]=cans;
}
int main(){
readi(N);
for(int i=1;i<=N;i++)readi(C[i]);
for(int i=1;i<N;i++)readis(X,Y),addudge(X,Y);
dfs1(1,0),dfs2(1,0);
for(int i=1;i<=N;i++)writip(ans[i]);
return 0;
}
浙公网安备 33010602011771号