[CF600E] Lomsat gelral 树上启发式合并

树上启发式合并qwq,一听就是很高级的算法,其实也不难。
这个算法主要也只能应用于:

  • 子树统计

  • 无修改操作

算法实现过程

首先进行轻重链剖分,和树剖不同的是,我们每次先dfs轻儿子,暴力统计答案大小,并且暴力删除轻儿子产生的影响。最后dfs重儿子,就是为了保留下重儿子的影响。
那时间复杂度为什么是对的呢?

只有dfs到轻边时,才会将轻边的子树中合并到上一级的重链,树链剖分将一棵树分割成了不超过logn条重链。
每一个节点最多向上合并logn次,单次修改复杂度O(1)。
所以整体复杂度是O(nlogn)的。

——摘自[trick]dsu on tree

那么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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值