CFP600E Lomsat gelral 学习笔记

CFP600E Lomsat gelral 学习笔记

Luogu Link

题意简述

给定一棵 \(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;
}
posted @ 2025-04-28 14:07  矞龙OrinLoong  阅读(8)  评论(0)    收藏  举报