树链剖分详解

最近一直在看熟练剖分,终于有点理解是怎么回事了,在网上看到这一篇博客还挺好的,就简单的改了改一些细节,树链剖分就是将一棵树按照一种规则分成若干条链,把每一条链看作是一个单位,用数据结构来维护这些链的值。

“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。

树链,就是树上的路径。剖分,就是把路径分类为重链和轻链记:
dep[v] :表示 v 的深度(根节点深度为1);
siz[v] : 表示以 v 为根的子树的节点数;
fa[v] : 表示 v 的父亲结点
top[v]: 表示 v 所在的重链的顶端节点;
son[v]:表示与v在同一重链上的 v 的儿子节点(姑且称为重儿子);
*w[v]v v 线西 log(N)$的时间完成原问题中的操作。

重儿子:siz[u]为v的子节点中siz值最大的,那么u就是v的重儿子。
轻儿子:v的其它子节点。
重边:点v与其重儿子的连边。
轻边:点v与其轻儿子的连边。
重链:由重边连成的路径。
轻链:轻边。

剖分后的树有如下性质:
性质1:如果(v,u)为轻边,则siz[u] * 2 < siz[v];
性质2:从根到某一点的路径上轻链、重链的个数都不大于logn。

算法实现:
我们可以用两个 dfs 来求出 fadepsizsontopw
dfs_1: fadepsizson 求出来,比较简单,略过。

dfs_2: 对于 v ,当 son[v]存在(即v不是叶子节点)时,显然有 top[son[v]]=top[v] 。线段树中,v的重边应当在v的父边的后面,记 w[son[v]]=totw+1 totw 表示最后加入的一条边在线段树中的位置。此时,为了使一条重链各边在线段树中连续分布,应当进行 dfs2(son[v])

对于v的各个轻儿子u,显然有 top[u]=u ,并且 w[u]=totw+1 ,进行 dfs2 过程。
这就求出了 top w
将树中各边的权值在线段树中更新,建链和建线段树的过程就完成了。

修改操作:例如将u到v的路径上每条边的权值都加上某值x。
一般人需要先求LCA,然后慢慢修改uv到公共祖先的边。而高手就不需要了。
f1=top[u]f2=top[v]
f1<>f2 时:不妨设 dep[f1]>=dep[f2] ,那么就更新 u f1的父边的权值 (logn) ,并使 u=fa[f1]
f1=f2 时: u v 在同一条重链上,若u与v不是同一点,就更新u到v路径上的边的权值 (logn) ,否则修改完成;
重复上述过程,直到修改完成。

求和、求极值操作:类似修改操作,但是不更新边权,而是对其求和、求极值。
就这样,原问题就解决了。画图来看看:树链剖分
这里写图片描述

如吓图所示,较粗的为重边,较细的为轻边。节点编号旁边有个红色点的表明该节点是其所在链的顶端节点。边旁的蓝色数字表示该边在线段树中的位置。图中 1491314 为一条重链。

当要修改 11 10 的路径时。

第一次迭代: u=11v=10f1=2f2=10 。此时 dep[f1]<dep[f2] ,因此修改线段树中的 5 号点,v=4,f2=1
第二次迭代: dep[f1]>dep[f2] ,修改线段树中10–11号点。 u=2f1=2
第三次迭代: dep[f1]>dep[f2] ,修改线段树中 9 号点。u=1f1=1
第四次迭代: f1=f2 u=v ,修改结束。

在树链剖分的题型中有边权型和点权型之分,点权的既叫好理解,就不多说了。边权就是将边映射到深度较深的那个点上。

// 点权型 单点更新 区间查询
#define lson l,m,rt<<1
#define rson m+1,r,rt<<1|1
const int Vmax = 5*1e4 + 5;//点的数量
const int Emax =2*1e5+5;//边的数量 小于Vmax的两倍
namespace segment_tree{
    int sum[Vmax<<2],add[Vmax<<2];
    inline void pushup(int rt){
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    void update(int L,int c,int l,int r,int rt=1){
        if (L == l && l == r)
        {
            sum[rt] = c;
            return ;
        }
        int m = (l + r) >> 1;
        if (L <= m) update(L , c , lson);
        else update(L , c , rson);
        pushup(rt);
    }
    int query(int L,int R,int l,int r,int rt=1){
        if (L <= l && r <= R)
            return sum[rt];
        int m = (l + r) >> 1;
        int ret = 0;
        if (L <= m) ret+=query(L , R , lson);
        if (m < R) ret+=query(L , R , rson);
        return ret;
    }
}
namespace poufen{
    using namespace segment_tree;
    int siz[Vmax],son[Vmax],fa[Vmax],dep[Vmax],top[Vmax],w[Vmax];
    int nodenum;

    struct edge{
        int v,next;
    }e[Emax];
    int pre[Vmax],ecnt;
    inline void init(){
        memset(pre, -1, sizeof(pre));
        ecnt=0;
    }
    inline void add_(int u,int v){
        e[ecnt].v=v;
        e[ecnt].next=pre[u];
        pre[u]=ecnt++;
    }
    void dfs(int u){
        siz[u]=1;son[u]=0;//下标从1开始,son[0]初始为0
        for(int i=pre[u];~i;i=e[i].next)
        {
            int v=e[i].v;
            if(fa[u]!=v)
            {
                fa[v]=u;
                dep[v]=dep[u]+1;//初始根节点dep!!
                dfs(v);
                siz[u]+=siz[v];
                if(siz[v]>siz[son[u]])son[u]=v;
            }
        }
    }
    void build_tree(int u,int tp){
        top[u]=tp,w[u]=++nodenum;
        if(son[u])build_tree(son[u],tp);
        for(int i=pre[u];~i;i=e[i].next)
            if(e[i].v!=fa[u]&&e[i].v!=son[u])
                build_tree(e[i].v,e[i].v);
    }

    inline int find1(int u,int v){
        int ret=0;
        int f1=top[u],f2=top[v];
        while(f1!=f2)
        {
            if(dep[f1]<dep[f2])
                swap(f1,f2),swap(u,v);
            ret+=query(w[f1],w[u],1,nodenum);
            u=fa[f1];
            f1=top[u];
        }
        if(dep[u]>dep[v])swap(u,v);
        ret+=query(w[u],w[v],1,nodenum);
        return ret;
    }
    int a[Vmax],b[Vmax];
    int val[Vmax];//
    void work1(int n)
    {
        memset(siz, 0, sizeof(siz));
        memset(sum, 0, sizeof(sum));

        init();
        int root=1;
        fa[root]=nodenum=dep[root]=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&val[i]);
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&a[i],&b[i]);
            add_(a[i],b[i]);
            add_(b[i],a[i]);
        }
        dfs(root);
        build_tree(root,root);
        for(int i=1;i<=n;i++)
            update(w[i],val[i],1,nodenum);
    }

}
using namespace poufen;
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值