树上启发式合并qwq,一听就是很高级的算法,其实也不难。
这个算法主要也只能应用于:
子树统计
无修改操作
算法实现过程
首先进行轻重链剖分,和树剖不同的是,我们每次先dfs轻儿子,暴力统计答案大小,并且暴力删除轻儿子产生的影响。最后dfs重儿子,就是为了保留下重儿子的影响。
那时间复杂度为什么是对的呢?
只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,树链剖分将一棵树分割成了不超过logn条重链。
每一个节点最多向上合并logn次,单次修改复杂度O(1)。
所以整体复杂度是O(nlogn)的。
那么CF600E就是这样一道题目啊!
#include<bits/stdc++.h>
#define maxn 100005
#define ll long long
using namespace std;
struct node{
int to;
node*next;
}*con[maxn];
void addedge(int x,int y)
{
node*p=new node;
p->to=y;
p->next=con[x];
con[x]=p;
}
int c[maxn],n,sz[maxn],a[maxn],maxv,hs[maxn];
ll sum,ans[maxn];
bool vis[maxn];
void calc(int v,int fa,int k)//计算贡献
{
c[a[v]]+=k;
if(k>0&&c[a[v]]>=maxv){
if(c[a[v]]>maxv) sum=0,maxv=c[a[v]];
sum+=a[v];
}
for(node*p=con[v];p;p=p->next)
if(p->to!=fa&&!vis[p->to]) calc(p->to,v,k);
}
void dfs1(int v,int fa)//轻重链剖分
{
sz[v]=1;
for(node*p=con[v];p;p=p->next)
if(p->to!=fa){
dfs1(p->to,v);
sz[v]+=sz[p->to];
if(sz[p->to]>sz[hs[v]]) hs[v]=p->to;
}
}
void dfs2(int v,int fa,bool tmp)
{
for(node*p=con[v];p;p=p->next)
if(p->to!=fa&&p->to!=hs[v]) dfs2(p->to,v,0);//先DFS轻儿子
if(hs[v]) dfs2(hs[v],v,1),vis[hs[v]]=1;//再DFS重儿子
calc(v,fa,1);ans[v]=sum;
if(hs[v]) vis[hs[v]]=0;
if(!tmp) calc(v,fa,-1),maxv=sum=0;//还原轻儿子
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<n;++i){
int x,y;
scanf("%d%d",&x,&y);
addedge(x,y);addedge(y,x);
}
dfs1(1,0);dfs2(1,0,0);
for(int i=1;i<=n;++i) printf("%lld ",ans[i]);
return 0;
}