浅谈树链剖分

Posted wr-eternity

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈树链剖分相关的知识,希望对你有一定的参考价值。

今天刚学会树剖。。。。。。(是不是觉得我很菜QwQ)

树剖的用处:

引子问题1:

  给你一颗树,支持两种操作:

    1、给x到y路径上的值加z

    2、求出点x的值

  简单,树上差分嘛,前几天刚学过啊。

引子问题2:

  给你一颗树,支持两种操作:

    1、给以x为根的子树加z

    2、求出以x为根的子树的和。

  简单,dfs序+线段树啊。

那么把两个问题结合起来呢?——树链剖分华丽丽登场!!!

树剖核心思想:

听说线段树挺好用的,区改区查只要log的复杂度,但是只能在线性结构上用,哎,真是太遗憾了。

听说有一种叫做dfs的东西可以把一棵树转化为线性结构。

深夜,某dalao拿着这两个东西搞来搞去然后——树链剖分就诞生啦!(好吧是我瞎yy的。。。)

没错,树剖就是把一棵树分成很多条链,使这些链的dfs序连续,从而把维护线性数据的数据结构搬到树上。

关于重链和轻链:

把树分成很多条链,那么应该怎么分呢?树剖发明人把树分为了重链和轻链,重链在dfs中的编号是连续的,也就是说我们可以一次直接从重链的顶端跳到重链的顶端,期间只要用个线段树维护一下就可以了,所以就非常快,诶,这就很优秀,但是轻链的话就只能一个一个慢慢跳,诶,很烦。

那么他是如何确定哪些链是重链,哪些链是轻链的呢?

对于树上的一个节点u,取其子树大小最大的儿子作为他的重儿子,那么重儿子和u的连边就是重边,由重边组成的链就是重链。

这个时候我们可能产生一个疑问:

  不是说重链跳得快吗,那为什么不取子树深度最大的作为中儿子而要取子树规模最大的呢?

这个问题就问的非常好,很有深度,不经过一定的思考是问不来这个问题的。

那么这是为什么呢?

很简单啊其实,注意到轻链只能一个一个跳,非常的凄惨,很慢,那么如果我们取的是子树规模较大的儿子为重儿子,那么一个轻节点就可以少跳几步就跳到一条重链上,然后搭个“顺风车”,诶,这就可以加速很多,而如果取子树深度较大的儿子为重儿子,那么轻节点可能就要多条几次才能跳到,而又优化的只是重链上的节点而已,对于大局而言这就很亏,诶,很不划算。(其实这是我实测出来的。。。)

树剖主体代码实现:

procedure dfs1(u,father,dep:longint);
var
    i,v:longint;
begin
    depth[u]:=dep; siz[u]:=1;                                //depth:深度,siz:子树规模
    i:=head[u]; faz[u]:=father;                              //faz:父亲
    while i<>0 do
    begin
        v:=vet[i];
        if v<>father then
        begin
            dfs1(v,u,dep+1);
            if siz[v]>siz[son[u]] then son[u]:=v;            //son:重儿子是哪个
            siz[u]:=siz[u]+siz[v];
        end;
        i:=next[i];
    end;
end;
procedure dfs2(u,father,t:longint);
var
    i,v:longint;
begin
    inc(time); i:=head[u];                                    //dfn:dfs序,top:链顶
    dfn[u]:=time; top[u]:=t;
    if son[u]=0 then exit;
    dfs2(son[u],u,t);                                         //这里先dfs遍历重儿子是为了让重链连续
    while i<>0 do
    begin
        v:=vet[i];
        if (v<>father)and(v<>son[u]) then dfs2(v,u,v);
        i:=next[i];
    end;
end;

更新、查询操作:

  更新的时候要把深度大的往上跳,避免出现擦肩而过的尴尬情况。

procedure update_path(x,y,z:longint);
var
    fx,fy:longint;
begin
    fx:=top[x]; fy:=top[y];
    while fx<>fy do
    begin
        if depth[fx]>depth[fy] then                            //选深度大的往上跳
        begin
            update(1,1,time,dfn[fx],dfn[x],z);                 //更新链上的值
            x:=faz[fx];                                        //可以一下跳到链顶,轻链的链顶就是它自己
        end else
        begin
            update(1,1,time,dfn[fy],dfn[y],z);                 //这里的update和下面的query是线段树的更新和查询操作
            y:=faz[fy];
        end;
        fx:=top[x]; fy:=top[y];
    end;
    if x<>y then                                               //感觉这句话没事么用处。。。。。。
        if dfn[x]>dfn[y] then update(1,1,time,dfn[y],dfn[x],z)
        else update(1,1,time,dfn[x],dfn[y],z)
    else update(1,1,time,dfn[x],dfn[y],z);
end;
function query_path(x,y:longint):longint;
var
    fx,fy:longint;
begin
    fx:=top[x]; fy:=top[y]; query_path:=0;
    while fx<>fy do
    begin
        if depth[fx]>depth[fy] then
        begin
            query_path:=(query_path+query(1,1,time,dfn[fx],dfn[x]))mod p;
            x:=faz[fx];
        end else
        begin
            query_path:=(query_path+query(1,1,time,dfn[fy],dfn[y]))mod p;
            y:=faz[fy];
        end;
        fx:=top[x]; fy:=top[y];
    end;
    if x<>y then
        if dfn[x]>dfn[y] then query_path:=(query_path+query(1,1,time,dfn[y],dfn[x]))mod p
        else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p
    else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p;
end;

树剖模板代码实现:

题目为洛谷P3384。

var
    dfn,fin,faz,siz,son,head,depth,top,a:array[0..100000]of longint;
    add_sum,sum:array[0..400000]of longint;
    next,vet:array[0..200000]of longint;
    i,n,m,root,p,x,y,z,time,tot,opt,q:longint;
function min(a,b:longint):longint;
begin
    if a<b then exit(a) else exit(b);
end;
function max(a,b:longint):longint;
begin
    if a>b then exit(a) else exit(b);
end;
procedure add_(x,y:longint);
begin
    inc(tot);
    next[tot]:=head[x];
    vet[tot]:=y;
    head[x]:=tot;
end;
procedure update(k,l,r,x,y,z:longint);
var
    mid:longint;
begin
    if (l>=x)and(r<=y) then
    begin
        add_sum[k]:=(add_sum[k]+z)mod p; exit;
    end;
    sum[k]:=(sum[k]+(min(r,y)-max(l,x)+1)*z mod p)mod p;
    mid:=(l+r)>>1;
    if x<=mid then update(k*2,l,mid,x,y,z);
    if y>mid then update(k*2+1,mid+1,r,x,y,z);
end;
function query(k,l,r,x,y:longint):longint;
var
    mid:longint;
begin
    if (l>=x)and(r<=y) then exit((sum[k]+(r-l+1)*add_sum[k]mod p)mod p);
    mid:=(l+r)>>1;
    query:=(min(r,y)-max(l,x)+1)*add_sum[k]mod p;
    if x<=mid then query:=(query+query(k*2,l,mid,x,y))mod p;
    if y>mid then query:=(query+query(k*2+1,mid+1,r,x,y))mod p;
end;
procedure dfs1(u,father,dep:longint);
var
    i,v:longint;
begin
    depth[u]:=dep; siz[u]:=1;
    i:=head[u]; faz[u]:=father;
    while i<>0 do
    begin
        v:=vet[i];
        if v<>father then
        begin
            dfs1(v,u,dep+1);
            if siz[v]>siz[son[u]] then son[u]:=v;
            siz[u]:=siz[u]+siz[v];
        end;
        i:=next[i];
    end;
end;
procedure dfs2(u,father,t:longint);
var
    i,v:longint;
begin
    inc(time); i:=head[u];
    dfn[u]:=time; top[u]:=t;
    fin[u]:=dfn[u]+siz[u]-1;
    if son[u]=0 then exit;
    dfs2(son[u],u,t);
    while i<>0 do
    begin
        v:=vet[i];
        if (v<>father)and(v<>son[u]) then dfs2(v,u,v);
        i:=next[i];
    end;
end;
procedure update_path(x,y,z:longint);
var
    fx,fy:longint;
begin
    fx:=top[x]; fy:=top[y];
    while fx<>fy do
    begin
        if depth[fx]>depth[fy] then
        begin
            update(1,1,time,dfn[fx],dfn[x],z);
            x:=faz[fx];
        end else
        begin
            update(1,1,time,dfn[fy],dfn[y],z);
            y:=faz[fy];
        end;
        fx:=top[x]; fy:=top[y];
    end;
    if x<>y then
        if dfn[x]>dfn[y] then update(1,1,time,dfn[y],dfn[x],z)
        else update(1,1,time,dfn[x],dfn[y],z)
    else update(1,1,time,dfn[x],dfn[y],z);
end;
function query_path(x,y:longint):longint;
var
    fx,fy:longint;
begin
    fx:=top[x]; fy:=top[y]; query_path:=0;
    while fx<>fy do
    begin
        if depth[fx]>depth[fy] then
        begin
            query_path:=(query_path+query(1,1,time,dfn[fx],dfn[x]))mod p;
            x:=faz[fx];
        end else
        begin
            query_path:=(query_path+query(1,1,time,dfn[fy],dfn[y]))mod p;
            y:=faz[fy];
        end;
        fx:=top[x]; fy:=top[y];
    end;
    if x<>y then
        if dfn[x]>dfn[y] then query_path:=(query_path+query(1,1,time,dfn[y],dfn[x]))mod p
        else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p
    else query_path:=(query_path+query(1,1,time,dfn[x],dfn[y]))mod p;
end;
begin
    read(n,q,root,p);
    for i:=1 to n do
            read(a[i]);
    for i:=1 to n-1 do
    begin
        read(x,y);
        add_(x,y); add_(y,x);
    end;
    dfs1(root,0,1);
    dfs2(root,0,root);
    for i:=1 to n do
        update(1,1,time,dfn[i],dfn[i],a[i]);
    while q>0 do
    begin
        read(opt);
        if opt=1 then
        begin
            read(x,y,z);
            update_path(x,y,z);
        end;
        if opt=2 then
        begin
            read(x,y);
            writeln(query_path(x,y));
        end;
        if opt=3 then
        begin
            read(x,z);
            update(1,1,time,dfn[x],fin[x],z);
        end;
        if opt=4 then
        begin
            read(x);
            writeln(query(1,1,time,dfn[x],fin[x]));
        end;
        dec(q);
    end;
end.

以上是关于浅谈树链剖分的主要内容,如果未能解决你的问题,请参考以下文章

浅谈树链剖分

浅谈树链剖分

蒟蒻浅谈树链剖分之一——两个dfs操作

浅谈树剖---树链剖分简易入门教程

浅谈树剖---树链剖分简易入门教程

BZOJ 2243--染色(树链剖分)