树上启发式合并可以用于解决子树的问题,但是不可以带修改。

来看一道例题:

CodeForces600C

题意:

给出一棵树,每个结点有一种颜色,求每个结点的子树中出现次数最多的颜色,如果有多个出现次数最多的颜色,则将它们相加。

思路:

如果只是求子树的种类数的话似乎用dfs序+莫队也可以做?

考虑最暴力的做法,枚举每个点,暴力访问子树中的点,统计完以后将统计的num数组清空。

其实num数组清空是浪费的,因为对于它的父亲结点,也会用到它的子树的信息。

启发式就体现在这里,对于一个结点,让大的子树不清空,所以先树剖出它的重儿子,重儿子就保留子树上的信息。

所以步骤是这样的:

1.树剖出重儿子;

2.DFS遍历子树,先遍历轻儿子,再遍历重儿子;

3.如果当前根结点是重儿子,就不将信息清空,否则就清空。

时间复杂度为为外部维护的时间。

时间复杂度的证明:

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

代码:

struct edge
{
    int to;
    int next;
}eg[200010];
int val[100010];
int head[100010];
int top;
int par[100010],son[100010],tot[100010];
int num[100010];//颜色出现的次数
int cnt;//当前出现最多的颜色出现的次数
ll sum;//当前答案
ll ans[100010];
bool vis[100010];
void add(int x,int y)
{
    eg[top].to=y;
    eg[top].next=head[x];
    head[x]=top++;
}
int dfs1(int id,int fa)//处理出重儿子
{
    par[id]=fa;tot[id]=1;
    int maxson=-1;
    son[id]=0;
    for(int i=head[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(temp==fa)continue;
        tot[id]+=dfs1(temp,id);
        if(tot[temp]>maxson)
        {
            son[id]=temp;
            maxson=tot[temp];
        }
    }
    return tot[id];
}
void calc(int id,int f)
{
    num[val[id]]+=f;
    if(f>0&&num[val[id]]==cnt)sum+=1ll*val[id];
    else if(f>0&&num[val[id]]>cnt)
    {
        cnt=num[val[id]];
        sum=1ll*val[id];
    }
    for(int i=head[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(temp!=par[id]&&!vis[temp])//重儿子不用访问,这里不用son[id]来判断的原因是轻儿子的重儿子仍然是要访问的
            calc(temp,f);
    }
}
void dfs2(int id,bool tp)//tp:是否是重儿子
{
    for(int i=head[id];i!=-1;i=eg[i].next)
    {
        int temp=eg[i].to;
        if(temp!=par[id]&&temp!=son[id])
            dfs2(temp,false);
    }
    if(son[id])
    {
        dfs2(son[id],true);
        vis[son[id]]=true;
    }
    calc(id,1);ans[id]=sum;
    if(son[id])vis[son[id]]=false;
    if(!tp)
    {
        calc(id,-1);
        cnt=0;sum=0;
    }
}
int main()
{
    int n,x,y;
    scanf("%d",&n);
    top=0;
    memset(head,-1,sizeof(head));
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;i++)
        scanf("%d",&val[i]);
    for(int i=0;i<n-1;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);add(y,x);
    }
    dfs1(1,0);
    sum=cnt=0;
    dfs2(1,0);
    for(int i=1;i<=n;i++)
        printf("%lld ",ans[i]);
    printf("\n");
    return 0;
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!

HDU6438 贪心+优先队列 Previous
HDU6435 k维最远曼哈顿距离 Next