树链剖分

#树链剖分

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

posted @ 2020-07-24 08:23  INFP  阅读(121)  评论(0编辑  收藏  举报