学习笔记:树链剖分

树链剖分是一种将树形结构转换为链状结构的技术,常用于处理路径修改操作。通过对树进行轻重链剖分,可以将树的路径转化为尽可能少的链,便于使用数据结构维护。该方法涉及重儿子、轻儿子、重边和轻边等概念,且在路径上轻链和重链的数量不超过log2n。实现时需要两次DFS来计算子树大小、深度、重儿子、父亲和树链信息。在查找路径时,通过重链顶端节点进行跳跃。实际应用中,难点在于选择合适的数据结构来维护这种链状结构。
摘要由CSDN通过智能技术生成

前言

对于树的形态不改变但是需要对路径进行修改操作的,数量剖分就是一个很好的选择,也就是把树弄成链状的然后用数据结构去维护它。

哎哎哎,我突然发现树剖出来的也是DFS序,只是一种特殊的DFS序而已,因此可以维护子树啊!

原理

为了把树上的东西变成线性的,我们采取的办法就是把树弄成若干条链,并且放在一个序列中,这样就可以被一般的数据结构维护了,然后链一段一段的选取修改或者查询,这就是数量剖分的含义了。

为了得到比较好的时间复杂度,我们希望对于任何路径都可以表示成尽量少的链,这里说一个最常用的方法,就是所谓的“轻重链剖分”。

引入几个定义:

重儿子:如果j是i的子树中节点数量最多的,则称j就是i的重儿子。

轻儿子:除重儿子以外的其它子节点。

重边:点i与重儿子的连边。

轻边:点i与其轻儿子的连边。

重链:由重边连成的路径。(有时候单独的结点也算)。

轻链:轻边。

就根据这写定义拆就好了。

性质

性质1:如果(i,j)为轻边,则sz[j]*2 < sz[i];

性质2:从根到某一点j的路径上轻链、重链的个数都不大于log2n。

(根据所有走的都是轻边,然后重边子树必须倍增这一点来证明)

实现思路

首先我们要维护:子树大小、深度(为了之后的查询)、重儿子、父亲、树链的位置,重链的顶端结点。

我们进行两次DFS

第一次:求出sz,dep,son,fa

第二次:求出tr,top(借助时间戳)

最后就是找路径:不停通过重链往上爬(爬重链顶端结点深的那个),直到两个结点所在重链的顶端结点相同,当然还没完,要把两个结点在同一条重链时的路径也算进去。

注意

没什么特别的,注意有的时候注意一下LCA那个点(比如修改边权的时候)

关于题目

其实根本就没什么难的,真的难的其实是用什么数据结构维护,也就是一种强行把线性的弄到树上去的一种操作而已。。。

核心代码

int dfn,sz[maxn],dep[maxn],son[maxn],fa[maxn],tr[maxn],top[maxn];
void DFS1(int i,int f,int d)
{
    sz[i]=1,dep[i]=d,fa[i]=f,son[i]=0;
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==f)continue;
        DFS1(j,i,d+1);
        if(sz[son[i]]<sz[j])son[i]=j;
        sz[i]+=sz[j];
    }
}
void DFS2(int i,int tp)
{
    top[i]=tp,tr[i]=++dfn;
    if(son[i])DFS2(son[i],tp);
    for(int p=first[i];p;p=E[p].next)
    {
        int j=E[p].to;
        if(j==fa[i] || j==son[i])continue;
        DFS2(j,j);
    }
}
int LCA(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]<dep[top[y]])swap(x,y);
        x=fa[top[x]];
    }
    return dep[x]<dep[y]?x:y;
}

至于那个LCA嘛,可以和修改操作放在一起写,没必要写把LCA算出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值