学习笔记——树链剖分

树链剖分

蒟蒻在做LCA时发现自己把树链剖分忘了QAQ,于是蒟蒻回来补一篇树链剖分。

本文大部分来自这里!

树剖是通过轻重边剖分将树分割成多条链,然后利用数据结构来维护这些链(本质上是一种优化暴力),保证每个点属于且只属于一条链,这里的数据结构包括树状数组、BST、SPLAY、线段树等(都不会QAQ

明确概念:

重儿子:父亲节点的所有儿子中子树结点数目最多(size最大)的结点;

轻儿子:父亲节点中除了重儿子以外的儿子;

重边:父亲结点和重儿子连成的边;

轻边:父亲节点和轻儿子连成的边;

重链:由多条重边连接而成的路径;

轻链:由多条轻边连接而成的路径;

蒟蒻抄来的图片
在这里插入图片描述
上面这幅图中,用黑线连接的结点都是重结点,其余均是轻结点,

2-11就是重链,2-5就是轻链,用红点标记的就是该结点所在重链的起点,也就是下文提到的top结点,

还有每条边的值其实是进行dfs时的执行序号。

变 量 声 明

const int maxn=1e5+10;
struct edge{
    int next,to;
}e[2*maxn];
struct Node{
    int sum,lazy,l,r,ls,rs;
}node[2*maxn];
int rt,n,m,r,a[maxn],cnt,head[maxn],f[maxn],d[maxn],size[maxn],son[maxn],rk[maxn],top[maxn],id[maxn];
名称解释
f[u]保存结点u的父亲节点
d[u]保存结点u的深度值
size[u]保存以结点u为根的子树节点个数
son[u]保存重儿子
rk[u]保存当前dfs标号在树中对应的节点
top[u]保存当前节点所在链的顶端节点
id[u]保存树中每个节点剖分以后的新编号(DFS的执行顺序)

具体实现

常见的路径剖分的方法是轻重树链剖分(启发式剖分)
一共2次bfs完成优化。

1,对于一个点我们首先求出它所在的子树大小,找到它的重儿子(即处理出size,son数组),
如图
在这里插入图片描述
比如说点1,它有三个儿子2,3,4

2所在子树的大小是5

3所在子树的大小是2

4所在子树的大小是6

那么1的重儿子是4

ps:如果一个点的多个儿子所在子树大小相等且最大

那随便找一个当做它的重儿子就好了

叶节点没有重儿子,非叶节点有且只有一个重儿子

2,在dfs过程中顺便记录其父亲以及深度(即处理出f,d数组),操作1,2可以通过一遍dfs完成

喜闻乐见抄的代码

void dfs1(int u,int fa,int depth)    //当前节点、父节点、层次深度
{
    f[u]=fa;
    d[u]=depth;
    size[u]=1;    //这个点本身size=1
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v==fa)
            continue;
        dfs1(v,u,depth+1);    //层次深度+1
        size[u]+=size[v];    //子节点的size已被处理,用它来更新父节点的size
        if(size[v]>size[son[u]])
            son[u]=v;    //选取size最大的作为重儿子
    }
}
//进入
dfs1(root,0,1);

解释

名称解释
f[u]保存结点u的父亲节点
d[u]保存结点u的深度值
size[u]保存以结点u为根的子树节点个数
son[u]保存重儿子
rk[u]保存当前dfs标号在树中对应的节点
top[u]保存当前节点所在链的顶端节点
id[u]保存树中每个节点剖分以后的新编号(DFS的执行顺序)

跑完就成了蒟蒻抄的另一个图片
在这里插入图片描述
3,第二遍dfs,然后连接重链,同时标记每一个节点的dfs序,并且为了用数据结构来维护重链,我们在dfs时保证一条重链上各个节点dfs序连续(即处理出数组top,id,rk)

void dfs2(int u,int t)    //当前节点、重链顶端
{
    top[u]=t;
    id[u]=++cnt;    //标记dfs序
    rk[cnt]=u;    //序号cnt对应节点u
    if(!son[u])
        return;
    dfs2(son[u],t);
/*我们选择优先进入重儿子来保证一条重链上各个节点dfs序连续,
一个点和它的重儿子处于同一条重链,所以重儿子所在重链的顶端还是t*/
    for(int i=head[u];i;i=e[i].next)
    {
        int v=e[i].to;
        if(v!=son[u]&&v!=f[u])
            dfs2(v,v);    //一个点位于轻链底端,那么它的top必然是它本身
    }
}

另 一 个 玄 学
在这里插入图片描述
4,两遍dfs就是树链剖分的主要处理,通过dfs我们已经保证一条重链上各个节点dfs序连续,那么可以想到,我们可以通过数据结构来维护一条重链的信息

5,树链剖分的时间复杂度
树链剖分的两个性质:
1,如果(u, v)是一条轻边,那么size(v) < size(u)/2;

2,从根结点到任意结点的路所经过的轻重链的个数必定都小于logn;

可以证明,树链剖分的时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

修改操作
1、单独修改一个点的权值
根据其编号直接在数据结构中修改就行了。

2、修改点u和点v的路径上的权值

(1)若u和v在同一条重链上
直接用数据结构修改pos[u]至pos[v]间的值。
(2)若u和v不在同一条重链上
一边进行修改,一边将u和v往同一条重链上靠,然后就变成了情况(1)。

例题

洛谷P3384
[NOI2015]软件包管理器
SDOI2011染色

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值