树链剖分
#树链剖分
1,将树从x到y结点最短路径上所有节点的值都加上z
这也是个模板题了吧
我们很容易想到,树上差分可以以O(n+m)的优秀复杂度解决这个问题
2,求树从x到y结点最短路径上所有节点的值之和
lca大水题,我们又很容易地想到,dfs O(n)预处理每个节点的dis(即到根节点的最短路径长度)
dis(到根节点最短路径长度)
lca性质:树上两点最短距离=dis(x)+dis(y)-2*dis(lca) O(mlogn+n)
树链剖分 就是对一棵树分成几条链,把树形变为线性,减少处理难度
需要处理的问题:
- 将树从x到y结点最短路径上所有节点的值都加上z
- 求树从x到y结点最短路径上所有节点的值之和
- 将以x为根节点的子树内所有节点值都加上z
- 求以x为根节点的子树内所有节点值之和
概念://可以先看代码再一一理解
-
重儿子:对于每一个非叶子节点,它的所有儿子中 儿子数量最多的那一个儿子 为该节点的重儿子
-
轻儿子:对于每一个非叶子节点,它的儿子中 非重儿子 的剩下所有儿子即为轻儿子
-
叶子节点没有重儿子也没有轻儿子(因为它没有儿子。。)非叶节点有且只有1个重儿子
-
重边:连接父亲节点与重儿子的边叫做重边
-
轻边:剩下的即为轻边
-
重链:相邻重边连起来的的路径叫重链
-
轻链:轻边连的路径
-
对于叶子节点,若其为轻儿子,则有一条以自己为起点的长度为1的链
变量声明:
\(fa[i]~i的爸爸,d[i]~i的深度,siz[i]i为根的子树节点个数,wson[i]保存重儿子,rk[i]保存当前dfs标号在树中对应节点\)
\(top[i]当前节点所在链顶端节点,id[i]~dfs执行顺序(剖分后新编号)\)
两遍dfs
dfs1():
处理出siz和son数组(如果一个点多个儿子子树相等且最大,随便找一个当重儿子)
顺便记录点的父亲和深度,处理fa和d数组,手动模拟一下吧
inline void dfs1(int x,int fa,int deep){
dep[x] = deep;//标记每个点深度
fa[x] = fa;
siz[x] = 1;//标记每个非叶子节点的子树大小,包含他自己
//int maxson = -1;//记录重儿子个数
for(int i = head[x];i;i=edge[i].next){
int y = edge[i].to;
if(y == fa) continue;
dfs1(y,x,deep+1);
siz[x] += siz[y]; //son的siz已被处理,更新fa的siz
if(siz[y] > siz[wson[x]]) wson[u]=y;//选最大siz
}
}
dfs2():
连接重链,标记每个点的dfs序,为了用数据结构维护重链,dfs时保证一条重链各个节点dfs序连续
即处理出top,id,rk
void dfs2(int u,int t){//t重链顶端
top[u] = t;//
id[u]=cnt++; //标记dfs序
rk[cnt] = u;//序号cnt对应节点v
if(!wson[u]) return;
dfs2(wson[u],t);
//优先选择进入重儿子来保证一条重链上各个节点dfs序连续
//一个点和它的重儿子处于一条同一重链,所以重儿子顶端还是t
for(int i=head[u];i;i=e[i].next){
int v = e[i].to;
if(v != wson[u] && v!=fa[u]) dfs2(v,v);//一个点位于链低端,那么它的top必然是它本身
}
}
为什么是dfs2(v,v)呢,因为当v是重儿子的时候,它不可能为一条链的顶,因为根据重边的定义,一定有一条边连向重儿子,若重儿子为顶,还会有一条边连向它,所以重儿子不会为顶端。
然后统计答案,中间一定需要别的数据结构
例题和代码就不放了,洛谷上有很多
pshttps://www.cnblogs.com/lykkk/p/10183778.html#autoid-3-0-0 这篇文章写的也很好
PS 一定要多写写,最好一周写一两道,要不然很容易bug