树链剖分初步学习

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34283998/article/details/78816635

树链剖分初步学习


类比线段树

在数据结构的处理中,我们会遇到一些单点/区间修改,单点/区间询问等问题。针对这类问题我们常常用线段树来解决。
但普通线段树能解决的问题都是在线性的,如果要在树上进行这些操作,那就是十分的困难。因此我们需要引用树链剖分来进行解决问题。

树链剖分

目的

正如上文所说,树链剖分用来解决一些在树上的信息维护问题。我们将树分为若干条链,然后用一些线性的数据结构维护信息。

方法

树链剖分的方法有三种:随机,盲目,启发式。往往在题目数据范围的限制下,我们使用启发式合并才是最佳的。然而启发式合并中最后的方法就是考虑当前节点的儿子中子孙最多的一个点连边。
这里就要引入几个概念了
重边:当前节点连向其子孙最多的儿子的边
轻边:不是重边的边
重链:多条重边在树上连起来的路径
图中红色的边是重边,红色边连起来的路径就是重链。
重边演示

注意:在有多个儿子拥有最多的子孙时,我们可以随机选择一个连上重链。

实现

因为目前所做的题目所限,处理线性的数据结构采用线段树进行处理。
总体的实现可以分为几个部分:
1. 找重边
2. 剖重链
3. 建线段树
4. 题目处理

找重边和剖重链,我们可以用两个dfs实现。第一个dfs对树进行一次遍历,处理每个节点的最大儿子编号,每个点的深度(后续的基本操作会用到),每个子树的大小。第二个dfs将按照dfs序将节点重新编号,并且储存当前节点所在重链的最高点(如果没有在重链中,就将其自己保存)。

    void dfs1(int u,int father,int deep)
    {
        fa[u]=father;//父节点
        deep[u]=deep;//深度
        siz[u]=0;//当前子树的大小
        for(int i=0;i<E[u].size();i++)
        {
            int v=E[u][i];
            if(v==father)
                continue;
            dfs(v,u,deep+1);
            siz[u]+=siz[v];
            if(son[u]==-1||siz[son[u]]<siz[v])
                son[u]=v;//最大儿子的编号
        }
    }

    void dfs2(int u,int last)
    {
        top[u]=last;//当前重链的最高点
        tree_num[u]=++dcnt;//dfs序
        rnk[dcnt]=u;//当前序号在树上的位置
        if(son[u]==-1)
            return;
        dfs2(son[u],last);
        for(int i=0;i<E[u].size();i++)
        {
            int v=E[u][i];
            if(v==fa[u]||v==son[u])//注意此处不仅要判断父亲,并且注意是否又走向最大的儿子(已经连过重边的)
                continue;
            dfs2(v,v);
        }
    }

接下来的操作是建立线段树,以及线段数的一堆操作。这部分比较基础,具体细节略过。

然后我们要根据问题的处理,弄出回答询问的函数。我们可以针对一个询问,在线段树上进行多种询问。
在这里我们要注意一下我们虽然可以在线段树上进行询问操作,但是我们还要知道要求的线段树范围。在这里我们可以通过之前处理出来的节点深度,进行判断和处理。
如果u和v所在重链的深度不同(u比较深),我们将u到u所在重链的顶点之间的ans求出,然后令u=top[u],重复此操作直到u和v在同一条链上。

通常情况下题目不仅要求我们询问,还有修改操作。一般分为单点修改和区间修改,两者大致相同。只是在区间修改中我们要使用懒标记。同时注意如果要使用懒标记我们在询问操作时可以将懒标记下传到儿子。

    #define lch rt*2
    #define rch rt*2+1
    int ans_sum,ans_min,ans_max;//最终答案
    struct Stree
    {
        int sum,max,min;//维护三种信息,和,最大值,最小值
        int flag;
    }Seg[MAXN*4+5];
    void Update(int rt)
    {
        Seg[rt].max=max(Seg[lch].max,Seg[rch].max);
        Seg[rt].min=min(Seg[lch].min,Seg[rch].min);
        Seg[rt].sum=Seg[lch].sum+Seg[rch].sum;
    }
    void pushdown(int rt)
    {
        if(!Seg[rt].flag)
            return;
        Seg[rt].max-=Seg[rt].flag,Seg[rt].min-=Seg[rt].flag,Seg[rt].sum-=Seg[rt].flag*siz[rnk[rt]];
        Seg[lch].flag+=Seg[rt].flag;
        Seg[rch].flag+=Seg[rt].flag;
        Seg[rt].flag=0;
    }
    void Build(int rt,int l,int r)
    {
        if(l==r)
        {
            Seg[rt].sum=Seg[rt].max=Seg[rt].min=val[rnk[l]];
            return;
        }
        int mid=(l+r)/2;
        Build(lch,l,mid);
        Build(rch,mid+1,r);
        Update(rt);
    }
    int Query_sum(int rt,int l,int r,int st,int ed)
    {
        if(st<=l&&r<=ed)
            return Seg[rt].sum;
        int mid=(l+r)/2;
        int t=0;
        pushdown(rt);
        if(st<=mid)
            t+=Query_sum(lch,l,mid,st,ed);
        if(ed>mid)
            t+=Query_sum(rch,mid+1,r,st,ed);
        return t;
    }
    int Query_max(int rt,int l,int r,int st,int ed)
    {
        if(st<=l&&r<=ed)
            return Seg[rt].max;
        int mid=(l+r)/2;
        int t=-INF;
        pushdown(rt);
        if(st<=mid)
            t=max(t,Query_max(lch,l,mid,st,ed));
        if(ed>mid)
            t=max(t,Query_max(rch,mid+1,r,st,ed));
        return t;
    }
    int Query_min(int rt,int l,int r,int st,int ed)
    {
        if(st<=l&&r<=ed)
            return Seg[rt].min;
        int mid=(l+r)/2;
        int t=INF;
        pushdown(rt);
        if(st<=mid)
            t=min(t,Query_min(lch,l,mid,st,ed));
        if(ed>mid)
            t=min(t,Query_min(rch,mid+1,r,st,ed));
        return t;
    }
    void Ask(int rt,int u,int v)
    {
        ans_sum=0,ans_max=-INF,ans_min=INF;
        while(top[u]!=top[v])
        {
            if(deep[top[u]]<deep[top[v]])
                swap(u,v);
            ans_sum+=Query_sum(1,1,n,tree_num[u],tree_num[top[u]]);
            ans_max=max(ans_max,Query_max(1,1,n,tree_num[u],tree_num[top[u]]));
            ans_min=min(ans_min,Query_min(1,1,n,tree_num[u],tree_num[top[u]]));
        }
        if(deep[u]>deep[v])
            swap(u,v);
        if(u!=v)
        {
            ans_sum+=Query_sum(1,1,n,tree_num[u],tree_num[v]);
            ans_max=max(ans_max,Query_max(1,1,n,tree_num[u],tree_num[v]));
            ans_min=min(ans_min,Query_min(1,1,n,tree_num[u],tree_num[v]));
        }
        return;
    }
    void change_point(int rt,int l,int r,int pos,int val)
    {
        if(l==r)
        {
            Seg[rt].sum=Seg[rt].max=Seg[rt].min=val;
            return;
        }
        int mid=(l+r)/2;
        if(pos<=mid)
            change_point(lch,l,mid,pos,val);
        else
            change_point(rch,mid+1,r,pos,val);
        Update(rt);
    }
    void change_S(int rt,int l,int r,int st,int ed,int val)
    {
        if(st<=l&&r<=ed)
        {
            Seg[rt].flag+=val;
            return;
        }
        int mid=(l+r)/2;
        if(st<=mid)
            change_S(lch,l,mid,st,ed,val);
        if(ed>mid)
            change_S(rch,mid+1,r,st,ed,val);
        Update(rt);
    }

以上大概是树链剖分的基本操作,还有一些特殊的操作因题而异,可能将在之后的题中展现出来。

猜你喜欢

转载自blog.csdn.net/qq_34283998/article/details/78816635