树链剖分总结

本文详细介绍了树链剖分算法,一种高效的解决树上问题的方法,尤其适用于查询树上两点的最近公共祖先(LCA)。文章通过实例展示了如何将树分为轻链和重链,并通过两次深度优先搜索实现LCA的快速查询。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

树链剖分,是很多树上问题很好的解决方法.                                                                                                                            比如说修改树上路径上点的权值.有些人说线段树一定能解决,但是光用线段树是不行的,这里就可以用线段树结合树链剖分解决.       相信很多人还不知道树链剖分是什么算法,不废话了,开始.

先来一道模板题,给定N个节点的一棵树有K次查询,每次查询a和b的最近公共祖先(即LCA)   (N<=1e5,K<=1e5)                             

首先暴力肯定会被卡掉O(N*K);  可以用倍增O(NlogN+KlogN),离线tarjan O(N+K) (常数较大且在强制在线时不行)                       LCA转RMQ利用dfs序也可以解决 O(2NlogN+K) ,今天我介绍的树链剖分虽然理论时间复杂度不够优秀,但是实际运行时间最优.     倍增216ms,离线tarjan128ms,LCA转RMQ260ms(在K远远大于N时此类问题它更优),而树剖108ms

下面我来介绍树剖的原理,它是把一棵树的所有边分成两种链(轻链和重链),重链的定义是在根节点一定的情况之下,在此节点的儿子节点中,谁下方的子树最大,就把那一条链定义成重链,此节点连接剩下子节点的边都定义为轻链.                                                 这有什么好处呢?请看图.                                                                                                                                                                                  

此图来自poj1330    剖分后就是如下情况,求LCA时遇到重链就一直到顶上,轻链就走一步.                                                                                                                             图画的略丑,但意思表达出来了.                                                                                                                                                 题时,可用几个数组将图的意思表达出来.                                                                                                                         size[]表示所有点子树的大小. fa[]表示此节点的父亲是谁.top[]表示以此节点为起点通过重链能到达最上面的点,dep[]表示此节点 深度,son[]表示此节点的儿子.   在这里我要说一下,如果一个节点有多个儿子子树个数相同时,那么就随便定义一个为重链就好了 .   链式前向星连边,两次dfs,第一次维护size,fa,dep,son.第二次维护top,之后遵循之前的原则,遇到重链跳到顶端,遇到轻链跳一步.暴力向上跳就行了.     代码如下:                                                                                                                                                  

#include<stdio.h>
int dep[100001];
int size[100001];
int son[100001];
int fa[100001];
int top[100001];
int head[100001];
int to[200001];
int next[200001],idx;
bool is[100001];
void addedge(int a,int b)
{
    next[++idx]=head[a];
    head[a]=idx;
    to[idx]=b;
}
void dfs(int p)
{
    size[p]=1;
    for(int i=head[p];i;i=next[i])
    {
        if(to[i]!=fa[p])
        {
            fa[to[i]]=p;
            dep[to[i]]=dep[p]+1;
            dfs(to[i]);
            size[p]+=size[to[i]];
            if(size[to[i]]>size[son[p]])
                son[p]=to[i];
        }
    }
}
void dfs2(int p,int t)
{
    top[p]=t;
    if(son[p])
        dfs2(son[p],t);
    for(int i=head[p];i;i=next[i])
    {
        if(to[i]!=fa[p]&&to[i]!=son[p])
            dfs2(to[i],to[i]);
    }   
}
int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(dep[top[x]]>dep[top[y]])
            x=fa[top[x]];
        else
            y=fa[top[y]];
    }
    if(dep[x]<dep[y])
        return x;
    else
        return y;
}   
int main()
{
    int Q,n,i,a,b,allfa,x,y;
    scanf("%d%d",&n,&Q);
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&a,&b);
        fa[b]=a;
        is[b]=true;
        addedge(a,b);
        addedge(b,a);       
    }   
    for(int i=1;i<=n;i++)
        if(is[i]==0)
        {
            allfa=i;    
            break;      
        }
    dfs(allfa);
    dfs2(allfa,allfa);
    for(int i=1;i<=Q;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));        
    }
}

LCA的结果就是最后x和y较浅值. 

 

树链剖分还有许多应用,比如它的dfs序与线段树的结合,再借用一下上张图.                                                                              此序列为: 8 4 10 16 3 12 11 2 15 7 1 14 13 5 9                       

 

                                                                                                                                                              

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值