• [学习笔记] 树链剖分


    今天刚学会树剖。。。。。。(是不是觉得我很菜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.
  • 相关阅读:
    Miox带你走进动态路由的世界——51信用卡前端团队
    从零开始搭建Vue组件库 VV-UI
    你不知的DOM编程
    浅谈前后端分离与实践(一)
    处理 Vue 单页面应用 SEO 的另一种思路
    Vue服务端渲染和Vue浏览器端渲染的性能对比
    实例PK(Vue服务端渲染 VS Vue浏览器端渲染)
    使用ES6+Vue+webpack+gulp构建新一代Web应用
    耐克的定制页用canvas如何实现....跪求前端大神指点。
    Unity热更新学习(二) —— ToLua c#与lua的相互调用
  • 原文地址:https://www.cnblogs.com/WR-Eternity/p/9901085.html
Copyright © 2020-2023  润新知