首页 > 其他分享 >树链剖分

树链剖分

时间:2023-02-03 10:03:19浏览次数:31  
标签:剖分 int top wson dfs 树链 dfn

“在一棵树上进行路径的修改、求极值、求和”乍一看只要线段树就能轻松解决,实际上,仅凭线段树是不能搞定它的。我们需要用到一种貌似高级的复杂算法——树链剖分。

树链剖分是将一棵树按照特殊的dfs序划分成链,从而使树上任意一条链最多被划分为log(n)段,同时保持的dfs序对子树操作的便利。

什么是树链剖分

  • 树链剖分,它可以对一棵树进行轻重链剖分后用数据结构来维护每条重链。
  • 比如下面这个问题:假设每个点有一个点权。如何把一棵树上的两个点uu,vv之间的简单路径上的所有点的点权增加dd?
  • 这就是树链剖分能够解决的的一个基本问题。

接下来介绍一下树链剖分的详细过程。

轻重链剖分

树链剖分的第一步就是将一棵树进行轻重链剖分。这一步决定了整个树链剖分的时间复杂度

引入几个概念:

  • size[u]:以u为根的子树大小

  • wson[u]:在u的儿子中size值最大的那一个,称作uu的重儿子

  • dfn[u]:每个点的dfs序号。pre[tot],如果dfn[u]=tot,则pre[tot]=u

  • 重链:指每个点与它的重儿子之间的连边(u—wson[u])

  • 轻链:在所有边中不是重的其他边
     

看图:

树链剖分_子树

如上图,每一个带红点的点就是轻儿子;每一条加粗的的边就是重链,没有加粗的就是轻边。比如说对于点2,那么
wson[2]=6,size[2]=5,size[wson[u]]=size[6]=3。
dfn[u]的含义就要稍微的特殊一些。因为它不仅仅是简单的dfs序号,而是按照轻重链的顺序来定义的。每次从一个点u往下dfs的时候,优先对wson[u]进行dfs,然后再遍历轻儿子们。
比如说上图,每条边上有一个序号。把这个序号看做是点的(比如说1->4上面的1应该是dfn[4])。从1开始dfs,可以明显的看到是先对4进行dfs,然后再对wson[4]也就是9进行dfs….到了叶子节点再回溯去dfs轻儿子。
为什么要这样做呢?因为这样之后,可以看到,每一条长重链上的每个点的dfn是连续的。比如1->4->9->13->14这条长重链上的dfn便是连续的,其他两条也一样。这样一来,就可以用维护区间的数据结构(比如线段树)来维护重链上的信息。

树链剖分的好处

  • 一个上面已经说了,就是重链上的dfn是连续的,可以用数据结构维护
  • 还有就是,在每一条由根到叶子结点的路径上,轻链的条数和重链的长度均不会超过logn。这个性质决定了树链剖分的时间复杂度。如果是拿线段树来维护链的话,复杂度就是nlog2n

代码实现轻重链剖分

  • 一共需要两个dfs。
  • 第一个用来处理每个点的基础信息(wson,size,dep,fa)

第一个:

inline dfs1(int u, int f)//两个参数:u是现在的点,f是u的父亲
{
    size[u] = 1;//最开始u的子树中只有u一个点
    for(edge *p = head[u]; p; p = p->next)//遍历每一个与u相连的点
    {
        int v = p->v;
        if(v == f)
            continue;//如果此点是u的父亲就跳过
        dep[v] = dep[u] + 1;
        fa[v] = u;//处理信息
        dfs1(v, u);//继续递归
        size[u] += size[v];//u的子树大小要加上v的子树大小
        if(size[wson[u]] < size[v])
            wson[u] = v;//更新重儿子
    }
}
  • 这样就用一个dfs处理出了每个点的信息。
  • 再用第二个dfs求出每个点的dfn以及该点所处的链的链首。

Code:

void dfs2(int u, int tp)//u是当前点,tp是该链链首
{
    dfn[u] = ++tot;
    pre[tot] = u,;
    top[u] = tp;
    //pre是dfn的反函数。如dfn[2] = 3,pre[3] = 2...
    //为什么要有这个反函数,因为在建线段树的时候是用dfn建的,然而如果想要知道原来那个点的信息就要知道pre
    if(wson[u])
        dfs2(wson[u], tp);//有重儿子就继续往下拉重链
    for(edge * p = head[u]; p; p = p->next)//回过头来在轻儿子中拉链
    {
        int v = p->v;
        if(v != fa[u] && v != wson[u])
            dfs2(v, v);//如果不是爸爸或重儿子,已该点为链首继续拉链
    }
}

线段树维护重链

  • 就是用线段树维护重链上的信息。
  • 注意事项:这里所有的参数都是以dfn的形象出现的,而不是原来那个点的序号。

Code:

void build(node *r, int left, int right)
{
    r->left = left;
    r->right = right;
    r->lazy = -1;
    r->s = 0;
    if(left == right)
    {
        r->s = value[pre[left]];
        //这里是pre[left]不是left!原因就是刚才说的:这里所有的参数都是以dfn的形象出现的,而不是原来那个点的序号。
        return ;
    }
    int mid = (left + right) / 2;
    node *lson = &pool[++cnt], *rson = &pool[++cnt];
    r->ch[0] = lson, r->ch[1] = rson;
    build(lson, left, mid);
    build(rson, mid + 1, right);
    pushup(r);
}

两点间路径的修改&查询

  • 就是最开始提到的问题:如何把一棵树上的两个点uu,vv之间的简单路径上的所有点的点权增加dd?

树链剖分_树链剖分_02

在同一重链上的两点都可以直接查询

例如求6与7之间的和
将6跳到1,同时答案加上query(dfn[2],dfn[6]);然后将7跳到1,答案加上query(dfn[3],dfn[7])

修改Code:

void modify(int u, int v, int d)
{
    while(top[u] != top[v]) //直到跳到一条链上
    {
        if(dfn[top[u]] < dfn[top[v]])
            swap(u, v);//这里根据链首的值交换一下
        change(root, dfn[top[u]], dfn[u], d), u = fa[top[u]];//最后u=链首的爸爸
    }
    if(dep[u] > dep[v])
        swap(u, v);
    change(root, dfn[u], dfn[v], d);
    //Q1
}

查询Code:

int Qsum(int u, int v)
{
    int ret = 0;
    while(top[u] != top[v])
    {
        if(dfn[top[u]] < dfn[top[v]])
            swap(u, v);
        ret += query(root, dfn[top[u]], dfn[u]), u = fa[top[u]];
    }
    if(dep[u] > dep[v])
        swap(u, v);
    ret += query(root, dfn[u], dfn[v]);
    return ret;
}

 

标签:剖分,int,top,wson,dfs,树链,dfn
From: https://blog.51cto.com/u_15952369/6034932

相关文章

  • 树链剖分入门
    目录树链剖分算法思想模版-树链剖分旅行P4374P4211CF1023FP1505P2486P7735P3976Trick总结树链剖分这玩意也是才开始预习,写得不好勿喷。约定记号:\(siz[x]\),\(x\)为根的......
  • 树链剖分学习笔记
    怕到时候忘了,来写一篇笔记前置芝士:树的存储与遍历,\(dfs\)序,线段树。树链剖分的思想及能解决的问题:树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。具体......
  • 树链剖分
    简述树链剖分用于将树分割成若干条链的形式,以维护树上路径的信息。具体来说,将整棵树剖分为若干条链,使它组合成线性结构,然后用其他的数据结构维护信息。树链剖分(树剖/链......
  • 【YBT2023寒假Day1 B】不跪模样(树链剖分)(线段树)
    不跪模样题目链接:YBT2023寒假Day1B题目大意给你一棵有根数,点有点权,两种操作:对于所有x子树内与x距离不超过2的点,将其点权加v。询问x子树中,满足i<=j且i,j......
  • 轻重链剖分板子
    //LuoguP3384【模板】轻重链剖分/树链剖分#include<iostream>#include<cstring>#include<algorithm>#include<vector>#definelcu<<1#definercu<<1|1usin......
  • 2568. 树链剖分
    2568.树链剖分原题链接思路:先将一颗树进行树链剖分,再将它的dfs序求出来利用dfs序将线段树模拟出来(build出来)再将输入的路径进行切割导入线段树处理,直到两个元素......
  • 算法学习笔记(6): 树链剖分
    树链剖分树链剖分是一个很神奇,但是在树上可以完成一些区间操作问题简单来说,就是把一棵树分成一条条的链,通过维护链上的信息来维护整棵树的信息基础知识可以参考我的另外......
  • 树链剖分 学习笔记
    树链剖分是一种思想,可以用来通过给树中每个点重新编号,从而让树中任意一条路径转化成\(O(logn)\)段连续区间(链)。树中任意一条路径均可以变成不超过\(logn\)段连续区间......
  • P2146 [NOI2015] 软件包管理器 树链剖分
    //题目大意:给定一棵树,树上的每个节点是一个软件,现在给出如下两个指令,install与uninstall,//如果需要installx号软件,那么我需要安装他到根节点之间的所有软件;如......
  • ZJOI2008, 树的统计 树链剖分模板
    //题意:给定一棵树,现在我需要询问以下操作//1.q,u之间的最小值//2.q,u之间的简单路径的权值和//3.修改树上q点的权值//思路:如果是在一段序列上的问......