树链剖分

树链剖分

English speaker can click here.

该文章难度较浅,一些巨佬们可自行忽视

引入

树链剖分就是把一棵树切成很多根链,那怎么去剖呢?这是有讲究的。

明确一些定义

重儿子:一个根节点下子树最大的那个儿子。

轻儿子:除了重儿子的所有儿子。

重边:与重儿子相连的边。

轻边:与轻儿子相连的边

重链:全部由重边组成的链

几条结论

内容

这是最关键的:任一节点到根节点到经过的轻边不超过logN

还有几条比较水的:

1.每一个节点最多属于一条重链

2.重链不相交

证明:

只证第一条:

从任一节点经过轻边到达上一层,经过一次轻边子树的个数会翻上1倍,所以容易得知一定少于logN。
其余的感性认识。

核心

数链剖分的本质就是讲一颗树分成若干个链的组合,从而做到将树状结构的问题转化到了线性结构。

预处理

首先呢,我们需要维护出每个节点的深度和他属于那个重链。

显而易见,为了维护重链,我们还需要知道每个节点对应的子树的大小。

当然,要想把树转换成线性结构,一个dfs序是很重要的,而且还要保证每一条重链上的dfs序是连续的。

所以我们肯定不能在一次dfs中完成以上的所有事情,所以两遍dfs是很重要。

关于,如何维护,每个人都有自己的做法。我提供我的伪代码。

#include <bits/stdc++.h>
using namespace std;
struct Node
{
    int fa;// 父亲是谁? 
    int dep;//我的传承有多长? 
    int pos;//我的重边走向何方? 
    int heavy;//我的重链家在何处? 
    int size;// 我子子孙孙有多少? 
    int ds;//dfs序 
}tr[MAXN];
vector<int>vec[MAXN];
void dfs1(int now,int fa)// 我想知道我爹和我失散多年的重儿子 
{
    tr[now].size = 1;
    tr[now].dep = tr[fa].dep+1;
    int mx = 0 ;
    for(int i = 0;i < vec[now].size();i++)
    {
        if(vec[now][i] != fa)
        {
            dfs1(vec[now][i],now);    
            tr[now].size += tr[vec[now][i]].size;
            if(tr[vec[now][i]].size > mx)
            {
                mx = tr[vec[now][i]].size;
                tr[now].pos = vec[now][i];
            }
        }    
    } 
}
int tot = 0;
void dfs2(int now,int fa)
{
    tr[now].ds = ++ tot;
    if(tr[fa].pos == now) 
    {
        tr[now].heavy = tr[fa].heavy;
    }
    dfs2(tr[now].pos,now);
    for(int i = 0;i < vec[now].size();i++)
    { 
        if(vec[now][i] != fa && vec[now][i] != tr[now].pos)
        {
            dfs2(vec[now][i],now);
        }
    }
} 

到这里,其实树链剖分的核心已经完成了。

线性处理

一般来说,传统意义上的树链剖分是会套上线段树,如果套上了splay的话,那么就会被称为LCT。

而线段树处理是,对于轻边链接的点我们使用单点修改,而重链则是直接跳到重链的顶端,重链位于一段连续的区间,所以,可以用区间维护。

这样的话因为我们的前置定理,所以树剖部分的复杂度最多为logN。

 

关于

其实树剖本身并不是一个完整的算法,其本质是用一些可以用在线性结构上的结构可以在多花费logN的前提下,去做树状结构的题

 

posted @ 2019-07-18 10:36  秘之洋洋  阅读(171)  评论(0编辑  收藏  举报