浅谈树链剖分

今天刚学会树剖。。。。。。(是不是觉得我很菜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.

原文地址:https://www.cnblogs.com/WR-Eternity/p/9901085.html

时间: 2024-08-02 19:48:05

浅谈树链剖分的相关文章

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

树链剖分,顾名思义就是将树形的结构剖分成链,我们以此便于在链上操作 首先我们需要明白在树链剖分中的一些概念 重儿子:某节点所有儿子中子树最多的儿子 重链:有重儿子构成的链 dfs序:按重儿子优先遍历时的顺序 轻儿子的意思就与重儿子相反 首先是第一个dfs操作 在本次操作中,我们主要做的是处理所有节点的父亲,子树大小,重儿子,深度等操作 void dfs1(int now,int father,int deep) { tree[now].depth=deep;//初始化当前节点的深度,子树大小,父

浅谈树链剖分(C++、算法、树结构)

关于数链剖分我在网上看到的有几个比较好的讲解,本篇主要是对AC代码的注释(感谢各位witer的提供) 这是讲解 http://www.cnblogs.com/kuangbin/archive/2013/08/15/3259083.html 另一个是百度文库 http://wenku.baidu.com/link?url=DY8CAbwdjitIiv8XQsHmVPi--dQAqw5z6dc_6N1Plh4u5Nfc1aCADQm4oAvt4Sqe1mXSixezzK4lRxofQKMX9cNzJ

POJ 2763 Housewife Wind(树链剖分+树状数组)

[题目链接] http://poj.org/problem?id=2763 [题目大意] 在一棵树上,给出一些边的边长,有修改边的边长的操作, 询问每次从当前点到目标点的最短距离 [题解] 树链剖分之后,相当于树状数组的单点更新和区间查询, 注意边权转点权之后链操作不覆盖deep最浅的点,这里容易出错 [代码] #include <cstdio> #include <cstring> #include <algorithm> using namespace std; c

bzoj 3637: Query on a tree VI 树链剖分 &amp;&amp; AC600

3637: Query on a tree VI Time Limit: 8 Sec  Memory Limit: 1024 MBSubmit: 206  Solved: 38[Submit][Status][Discuss] Description You are given a tree (an acyclic undirected connected graph) with n nodes. The tree nodes are numbered from 1 to n. Each nod

树链剖分——树形到线性的转化

树链剖分: 树上操作并不能实现一段链的直接更新. 树链剖分就解决了这个问题. 本质上是树形到线性的转化. 通过子树,重链是一个连续的dfn区间的优秀性质,可以在dfn序列上进行操作,达到在树上操作的目的. 通常和线段树结合. 板子:以前写的. 树链剖分 例题: 1.遥远的国度 题目大意: 给定一棵有根树,每个点有一个权值,提供三种操作: 1.将x节点变为根节点 2.将x到y路径上的点的权值全部改为v 3.询问x的子树中点权的最小值 如果根不变,那么2.3就直接做了. 但是根变化了,随之第三问,子

午学树链剖分有感~

今天上课谈到树链剖分,被机房某dalao嘲讽了一波,决定冒着不听课的风险学链剖 关于这篇blog的灵感来源:传送门,不妥删 1.0前置知识 1.1链式向前星 这个应该都会吧,但还是附上讲解,这个图很易懂啊,蒟蒻当时一直没学懂,看了图才发现自己有多弱智 1.2dfs序 这个学长上课讲的随便找了一个讲解,仅供参考:传送门 1.3线段树 线段树应该都学了,就不附连接了 1.4为什么要学链剖 小蒟蒻没怎么做过树上倍增和链剖的题,但据说树上倍增的题链剖都能做,但链剖的题树上倍增不一定能做 2.0正文 2.

Luogu P3384 【模板】树链剖分

传送门~ 树链剖分,顾名思义,就是把树分成链. 通过这个方法,可以优化对树上两点间路径.某一点子树的修改和查询的操作,等. 流程 $dfs1()$ 在这个函数中,要处理出每个节点的: 深度dep[] 父亲fa[] 大小siz[] 重儿子编号hson[] 一个节点的siz[],是包括它自己.它的儿子.它儿子的儿子……一共的节点数量. 所谓的重儿子,就是一个节点的儿子中,siz[]最大的那一个. 叶子节点没有儿子,所以也没有重儿子. 这个函数就是普通的遍历整棵树,每到一个点记录dpth[],siz[

树链剖分 [模板]最近公共祖先LCA

本人水平有限,题解不到为处,请多多谅解 本蒟蒻谢谢大家观看 题目:传送门 树链剖分:跑两遍dfs,第一遍找重边,第二遍找重链. 重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点: 轻儿子:父亲节点中除了重儿子以外的儿子: 重边:父亲结点和重儿子连成的边: 轻边:父亲节点和轻儿子连成的边: 重链:由多条重边连接而成的路径: 轻链:由多条轻边连接而成的路径 son[]表示重儿子,top[]表示重链所在的第一个节点,sz[]表示子节点数,fa[]表示父亲节点 图示: code: #i

[总结]树链剖分的详细介绍

目录 一.关于树链剖分 二.树链剖分实现流程 二.树链剖分具体实现 1.需要表示的变量 2.储存一棵树 3.第一次遍历,处理fa,dep,size,son数组 4.第二次遍历,处理top,seg,rev数组 5.初始化线段树 6.单点修改 7.区间修改---以x为根结点的子树内节点的值都加val 8.区间修改---节点x到节点y的最短路径中同时加val 9.区间查询---以x为根结点的子树内节点的值的和 10.区间查询---节点x到节点y的最短路径中节点的和 11.区间查询---节点x到节点y的