树链剖分
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的前提下,去做树状结构的题