树链剖分

codesonic

2018-03-29 14:58:59

Personal

### 树链剖分 --- 树链剖分是把一个树上问题转换为区间问题的方法 例题 [P3384](https://www.luogu.org/problemnew/show/P3384) 考虑一个简化版的问题 给你一棵树,有两种操作: 1.给x的子树每个节点加上a 2.询问x的子树和 我们对这棵树进行dfs遍历,记录下所有节点的dfs序,如下图: ![](https://cdn.luogu.com.cn/upload/pic/16405.png) 观察任意一个节点的子树的dfs序,可以发现每个子树都是一个区间 于是可以利用这个性质来维护 因为是区间问题,随便套一个数据结构就可以了 接下来我们看链问题的解决方式 如果利用这个性质,我们发现链在这棵树上被分成许多段 如从dfs序6到dfs序9,被分成了 [6],[4],[1-2],[7-9]四段 能不能找出一种dfs的方式,使得被分成的段在平均情况下尽量少呢? 对于一个显然我们可以用贪心的方式,进行dfs序遍历时优先遍历比较大的子树。而定义对于每一个节点,通往最大的子树的那条边称作重边,最大的子树的顶点叫做重儿子 关于重链,可以看一下这张经典的图,其中加粗的边就是重边,没有点红点的就是重儿子 ![](http://odwv9d2u8.bkt.clouddn.com/17-11-12/80717953.jpg) 现在我们来分析一条链最多会被分成几段 对于任意一个有两个子树以上的节点,链都有两个“走向”,一是走非重边,二是走重 有两个重要的定理 1.如果边(u,v)是轻边,那么size(v)<=size(u/2) 2.从根到某节点的路径上,不超过logn条重链和logn条轻链 接下来结合一下代码 代码分为两次dfs,第一次是求出子树大小 代码如下: ```cpp void dfs(int u,int fa) { int i=0; size[u]=1; r[u]=r[fa]+1;//r:深度 father[u]=fa; for(i=first[u];i;i=nxt[i])//对i的每条出边 if(a[i]!=fa) {//若该出边连向儿子 dfs(a[i],u); size[u]+=size[a[i]];//size:子树大小 } } ``` 这部分代码很好理解,不多讲了 接下来是第二次dfs,是对于每个节点,向下拉一条重边