浅谈树链剖分

Something about 树链剖分

  • 线段树是不能在树上进行维护的,因为线段树是维护线性或者近似线性的数据

  • 听见这个名字是在看到许多大神都在学习这个算法,看见题目就说树剖,树剖。。树剖有那么强大?

  • 其实它并不难,就是将树上的节点根据某些性质,人为的编号,形成一条条链,再用线段树等数据结构维护。这些链称为树链,这个过程就是剖分

用途  

问题:给你一棵树,每条边有边权,请你实现下列一系列操作

  • 查询u与v之间的路径上,边权之和

  • 查询u到v之间的路径上,最大值

  • 给u到v之间的路径上每一条边加上一个值

  • ......

我们需要知道的

  • hson[]表示当前点的重儿子

  • size[]表示以当前点为根的子树大小

  • fa[]表示当前点的父亲,根节点的父亲为本身

  • top[]表示当前点所处的链,其链头的节点编号,或者说是链中,深度最小的节点。

  • dep[]表示当前点的深度;深度为当前点与根节点的距离+1

一些定义(我会让你们非常理解的)

  • 重儿子 如果当前点的儿子中,size最大的;如果有多个,就随便选一个

  • 轻儿子 非重儿子的其他儿子节点

  • 重边 当前点与其重儿子所连起来的边

  • 轻边 除了重边外的其他边

  • 重链 所有重边一起连起来的一条链;可以将轻儿子自己理解为一条重链上的点,链头为本身

  • 轻链 轻边,不是所有轻边连起来的链的意思。

如何编号

  就上面的图进行说明

  我们先找重儿子,然后再找轻儿子,递归下去

  也可以说将先找到的重链上的点放在线段树对应的位置上

  如图,我们以此寻找到的顺序为1,6,8,11,13,17,12,7,9,5,10,2,4,14,16,15,3

  可以理解为是优先选择重儿子的DFS序。

  我们务必要保证同一条重链上的点要按顺序的摆放在线段树(别的数据结构)上,轻儿子后放

  就这个图而言,线段树(别的数据结构)上位置1表示树上位置1,位置2表示树上位置6,位置3表示树上位置8,以此类推。。。

一些性质

  • 轻边(U,V),size(V)<=size(U)/2。 ž

  • 从根到某一点的路径上,不超过O(logN)条轻边,不超过O(logN)条重链。

如何处理

第一次DFS

我们需要求出

  • 父亲节点fa,深度dep,子树大小size,重儿子hson

procedure dfs1(x,now,q:longint);//x表示当前节点,now表示深度,q表示当前点父亲节点;这里连的双向边,用前向星存。
var
        k,maxn:longint;//k为前向星调用所需要的变量,maxn用来记录最大子树大小
begin
        fa[x]:=q;
        size[x]:=1;
        dep[x]:=now;

        maxn:=-maxlongint;

        k:=l[x];
        while k<>0 do
        begin
                if d[k]<>q then
                begin
                        dfs1(d[k],now+1,x);

                        size[x]:=size[x]+size[d[k]];

                        if size[d[k]]>maxn then
                        begin
                                hson[x]:=d[k];

                                maxn:=size[d[k]];
                        end;//寻找重儿子
                end;

                k:=pre[k];
        end;
end;

第二次DFS

我们需要求出

  • 树上每一个节点的位置,在线段树(别的数据结构)上的位置,以便维护

  • 每一条重链的链头,不在重链上的点,其链头为本身

procedure dfs2(x,head,q:longint);//x表示当前点;head表示链头;q表示当前点父亲节点
var
        k:longint;
begin
        top[x]:=head;

        inc(tot);
        p[x]:=tot;//p[]表示树上节点在线段树上的下标
        pp[tot]:=x;//线段树上的下标对应的树上节点位置

        if hson[x]<>0 then
                dfs2(hson[x],head,x);//优先选择重儿子

        k:=l[x];

        while k<>0 do
        begin
                if (d[k]<>q) and (d[k]<>hson[x]) then
                        dfs2(d[k],d[k],x);

                k:=pre[k];
        end;
end;

 一些线段树可行的操作

指的是树上查询,修改等操作

因为u和v之间的路径,其编号必定不是连续的,所以我们要一段段区间的查询;

其实就是两种情况

  (1)点u和点v在同一条重链上

  这种情况,因为是在同一条重链上,所以在线段树上必定表示为一段区间,所以直接查询(修改)

  (2)点u和点v不在同一条重链上

  我们尽量往重链上靠,查询(修改)当前点到其链头这段区间的值,然后跳到其链头的父亲。当然,我们优先跳两点的链头深度最大的,类似于LCA

while top[x]<>top[y] do//不在一条链上,情况(1)
begin
        if dep[top[x]]<dep[top[y]] then
                swap(x,y);//优先跳

        find(1,1,n,p[top[x]],p[x]);//查询(修改)

        x:=fa[top[x]];
end;

if dep[x]>dep[y] then//以下都是情况(2)
        swap(x,y);

find(1,1,n,p[x],p[y]);

时间复杂度

  学习每个算法最麻烦的就是算其时间复杂度了。乍一看会超时?可是有上面所写的性质,故效率为n log n级别的

  O(mn log² n)(m表示修改查询次数)

推荐题目

学完一个算法,当然要练练手

  • [ZJOI2008]树的统计 [ZJOI2008]树的统计

  • SPOJ375 Query on a tree

  • BZOJ2157 旅游

原文地址:https://www.cnblogs.com/philchieh/p/7705019.html