SkyWT / 博客 / 树链剖分(Heavy-Light Decomposition)小结

树链剖分(Heavy-Light Decomposition)小结

2019 年 3 月 21 日 02:26


文章目录

树链剖分,可以把一棵树划分成多条链,对于每条链可以用线段树等数据结构进行维护,将树形结构的问题转化。

常见的方法是轻重链剖分,即 Heavy-Light Decomposition。

[notice]这是一篇意识流式的总结,如果要入门树链剖分请不要看这篇……[/notice]

前置知识

LCA、线段树……

一些定义

需要构造如下数组:

  • size[x]:以 x 为根的子树的节点总数;
  • deep[x]:x 的深度;
  • son[x]:x 通过重链通向的儿子节点;
  • fa[x]:x 的父节点;
  • top[x]:x 所在重链的顶端节点;
  • id[x]:节点 x 在线段树中的下标位置;
  • v[x]:节点 x 的权值(对于边权,可以把边的权值归属于其下面的点,转化为点权处理)。

另建一棵线段树存储重链信息。

如果 v 是 u 的儿子中 size 最大的一个,则 (u,v) 是重边,v 是 u 的重儿子,u 通向的其他儿子是轻边

连续的重边形成重链。通过 id[] 可以将一条重链映射到线段树上的一条线段。

关于复杂度的结论

结论一: 如果 (u,v)(u,v) 为轻边,则 size[v] 不会大于 size[u]/2
结论二: 重链和轻边的数量级相当。
由此可以得到:
结论三: 从某一个点到根上经过的重链或轻边的总数在 log(n)\log (n) 级别。这是树链剖分的核心。

据此,对于一条链上的操作可以做到 log(n)\log(n)。对于链上的修改,整条重链可以对应线段树的一个区间,存储 top[] 可以实现直接调到重链顶端,达到优化时间复杂度的目的。

实现

第一遍 DFS 构造出 size[],son[],fa[],deep[],第二遍 DFS 造出 top[],id[]

对于两点的路径修改,直接求出 LCA 后一直往上跳即可。
对于子树修改,直接当成 DFS 序处理。

对于构造 id[] 的 DFS 需要注意:本质上 id[] 是一个时间戳,即 DFS 序。为了使得重边在造出来的序中连续,便于线段树的处理,在遍历的时候要先遍历重儿子。涉及子树的操作也会方便很多。

代码

洛谷模板题 P3384:

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cmath>

using namespace std;
// #define int long long

inline int read(){
    int ret=0,f=1;char ch=getchar();
    while (ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=getchar();}
    while (ch>='0'&&ch<='9') ret=ret*10+ch-'0',ch=getchar();
    return ret*f;
}

const int maxn=1e5+5;
const int maxe=2*maxn;

int n,m,root,tt;

void add(int &x,int y){
    x=(x+y%tt)%tt;
}

class SegmentTree{

    #define mid (((tr-tl)>>1)+tl)
    #define ls ((p<<1))
    #define rs ((p<<1)+1)

    private:
        int tree[maxn*4],tag[maxn*4];

        void push_down(int tl,int tr,int p){
            add(tree[ls],tag[p]*(mid-tl+1)%tt);
            add(tag[ls],tag[p]);
            add(tree[rs],tag[p]*(tr-(mid+1)+1)%tt);
            add(tag[rs],tag[p]);
            tag[p]=0;
        }

    public:
        SegmentTree(){
            memset(tree,0,sizeof(tree));
            memset(tag,0,sizeof(tag));
        }

        void update(int x,int tl,int tr,int p,int delta){
            if (tl==tr && tl==x){
                add(tree[p],delta);
                return;
            }
            push_down(tl,tr,p);
            if (x<=mid  ) update(x,tl,mid,ls,delta);
            if (mid+1<=x) update(x,mid+1,tr,rs,delta);
            tree[p]=(tree[ls]+tree[rs])%tt;
        }

        void update(int sl,int sr,int tl,int tr,int p,int delta){
            if (sl<=tl && tr<=sr){
                add(tree[p],(tr-tl+1)*delta%tt);
                add(tag[p],delta);
                return;
            }
            push_down(tl,tr,p);
            if (sl<=mid  ) update(sl,sr,tl,mid,ls,delta);
            if (mid+1<=sr) update(sl,sr,mid+1,tr,rs,delta);
            tree[p]=(tree[ls]+tree[rs])%tt;
        }

        int query(int x,int tl,int tr,int p){
            if (tl==tr && x==tl) return tree[p]%tt;
            push_down(tl,tr,p);
            int ret=0;
            if (x<=mid  ) add(ret,query(x,tl,mid,ls));
            if (mid+1<=x) add(ret,query(x,mid+1,tr,rs));
            return ret;
        }

        int query(int sl,int sr,int tl,int tr,int p){
            if (sl<=tl && tr<=sr) return tree[p]%tt;
            push_down(tl,tr,p);
            int ret=0;
            if (sl<=mid  ) add(ret,query(sl,sr,tl,mid,ls));
            if (mid+1<=sr) add(ret,query(sl,sr,mid+1,tr,rs));
            return ret;
        }
};

int tot=0,lnk[maxn],nxt[maxe],to[maxe];
int size[maxn],deep[maxn],fa[maxn];
int v[maxn];

void add_edge(int x,int y){
    tot++;to[tot]=y;
    nxt[tot]=lnk[x];lnk[x]=tot;
}

int f[maxn][25];

int get_lca(int x,int y){
    if (deep[x]<deep[y]) swap(x,y);
    for (int i=23;i>=0;i--) if (deep[f[x][i]] >= deep[y]) x=f[x][i];
    if (x==y) return x;
    for (int i=23;i>=0;i--) if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
    return fa[x];
}

namespace HLD{
    int son[maxn],top[maxn],id[maxn];

    void make_tree(int x){
        size[x]=1;
        f[x][0]=fa[x];for (int i=1;i<=23;i++) f[x][i]=f[f[x][i-1]][i-1];
        int max_size=0,k=-1;
        for (int i=lnk[x];i;i=nxt[i]) if (to[i]!=fa[x]){
            fa[to[i]]=x;deep[to[i]]=deep[x]+1;
            make_tree(to[i]);
            size[x]+=size[to[i]];
            if (size[to[i]]>max_size) max_size=size[to[i]],k=to[i];
        }
        if (k!=-1) son[x]=k;
    }

    void make_chain(int x){
        id[x]=++id[0];
        if (son[fa[x]]!=x) top[x]=x; else top[x]=top[fa[x]];
        if (son[x]) make_chain(son[x]);
        for (int i=lnk[x];i;i=nxt[i]) if (to[i]!=fa[x] && to[i]!=son[x]){
            make_chain(to[i]);
        }
    }

    void change_single(int x,int l,int delta,SegmentTree &tree){
        while (x!=l){
            if (top[x]!=x){
                if (deep[top[x]]>=deep[l]) tree.update(id[top[x]]+1,id[x],1,n,1,delta),x=top[x];
                else tree.update(id[l]+1,id[x],1,n,1,delta),x=l;
            } else tree.update(id[x],1,n,1,delta),x=fa[x];
        }
        tree.update(id[x],1,n,1,delta);
    }

    void change_chain(int x,int y,int delta,SegmentTree &tree){
        int l=get_lca(x,y);
        change_single(x,l,delta,tree);
        change_single(y,l,delta,tree);
        tree.update(id[l],1,n,1,-delta);
    }

    int query_single(int x,int l,SegmentTree &tree){
        int ret=0;
        while (x!=l){
            if (top[x]!=x){
                if (deep[top[x]]>=deep[l]) add(ret,tree.query(id[top[x]]+1,id[x],1,n,1)),x=top[x];
                else add(ret,tree.query(id[l]+1,id[x],1,n,1)),x=l;
            } else add(ret,tree.query(id[x],1,n,1)),x=fa[x];
        }
        add(ret,tree.query(id[x],1,n,1));
        return ret;
    }

    int query_chain(int x,int y,SegmentTree &tree){
        int l=get_lca(x,y);
        return (query_single(x,l,tree)+query_single(y,l,tree)-tree.query(id[l],1,n,1)+tt)%tt;
    }

    void change_subtree(int x,int delta,SegmentTree &tree){
        tree.update(id[x],id[x]+size[x]-1,1,n,1,delta);
    }

    int query_subtree(int x,SegmentTree &tree){
        return tree.query(id[x],id[x]+size[x]-1,1,n,1);
    }
}

SegmentTree tree;

signed main(){
    n=read(),m=read(),root=read(),tt=read();
    for (int i=1;i<=n;i++) v[i]=read();
    for (int i=1;i<n;i++){
        int x=read(),y=read();
        add_edge(x,y);add_edge(y,x);
    }

    deep[root]=1;
    HLD::make_tree(root);
    HLD::make_chain(root);

    for (int i=1;i<=n;i++) tree.update(HLD::id[i],1,n,1,v[i]);

    while (m--){
        int opt=read(),x,y,z;
        switch(opt){
            case 1:
                x=read(),y=read(),z=read()%tt;
                HLD::change_chain(x,y,z,tree);
                break;
            case 2:
                x=read(),y=read();
                printf("%d\n",HLD::query_chain(x,y,tree));
                break;
            case 3:
                x=read(),z=read()%tt;
                HLD::change_subtree(x,z,tree);
                break;
            case 4:
                x=read();
                printf("%d\n",HLD::query_subtree(x,tree));
                break;
        }
    }

    return 0;
}

暂无评论


发表新的评论

所有评论都将经过博主审核。请勿填写无意义邮箱或发表无关评论、广告等,否则会被视为垃圾评论。

提交评论即表明你同意本网站使用 Cookie,并允许本站在后台记录你的邮箱、IP 地址等必要信息。这些信息不会被透露给其他用户。(提交一次评论后,本提示将不再展示)