【算法学习】树链部分

说句闲话

这个东西已经20多天没写了,感觉已经忘没了,非常糟糕,只好赶快补一补,确实还是得多打代码,不然都忘光就丸辣。

前言

树链问题通常是关于树上路径的操作,将路径拆分成一条条链,然后用线段树维护链的权值。

注:本题解并不适用于毫无基础的oier,只是做简单讲解,想了解具体定义请自行查阅。

树链模板题

我们一下都围绕这道题讲述操作,我们需要实现:

  • 两点路径权值加;

  • 两点路径权值和;

  • 节点子树权值加;

  • 节点子树权值和;

2次dfs成链

第一次

我们先用第一次 dfs 找到每个节点的以下东西(不懂先放着,继续向下看):

  • 节点深度:为了查路径时确定哪个作为起点终点及先动深度更深的点。

  • 节点大小:一个子树是一个连续的dfs序,为了操作整颗子树。

  • 父节点:当达到链顶时为了转移到另一个链上。

  • 重儿子:为了形成重链。

void dfs1(int x,int f,int d){//当前节点,父节点,深度
	deep[x]=d;//深度 
	siz[x]=1;//大小 
	fa[x]=f;//父节点 
	int mxson=-1;//重儿子大小 
	for(int y:v[x]){
		if(y==f)
			continue;
		dfs1(y,x,d+1);
		siz[x]+=siz[y];
		if(siz[y]>mxson){//比当前已有重儿子更大,更新 
			son[x]=y;
			mxson=siz[y]; 
		} 
	}
}

第二次

这一次我们要形成树链,优先遍历重链,记录每个点以下内容:

  • dfs序赋新节点编号:为了线段树的节点标号。

  • 新节点编号的权值储存:初始值转移过来。

  • 记录链顶:为了求路径和,你先别急了解他。

void dfs2(int x,int topf){//当前节点,链顶节点 
	id[x]=++cnt;//dfs序 
	w[cnt]=aa[x];//初始化的值转移过来 
	top[x]=topf;//更新链顶 
	if(!son[x]){//叶子节点滚! 
		return;
	} 
	dfs2(son[x],topf);//有点遍历重儿子形成重链 
	for(int y:v[x]){
		if(y!=fa[x]&&y!=son[x]){
			dfs2(y,y);//轻儿子链顶是自己 
		}
	}
}

处理链

大佬的图
https://www.cnblogs.com/chinhhh/p/7965433.html大佬的图

子树处理

因为优先遍历重儿子,所有重儿子上 dfs 序是连续,仔细想一下遍历过程就可以发现同一子树上的编号也是连续的,所以子树操作就变成了线段树区间加区间和

具体如下:(省略了线段树操作)

change(1,1,n,id[x],id[x]+siz[id[x]]-1,k); 
query(1,1,n,id[x],id[x]+siz[id[x]]-1);

路径处理

这个可以模拟一下,假设我们求 \(4,7\) 的路径上和,他们不在同一条链上,我们找到链顶深度更深的节点操作(7所在的链),那 \(7->6\) 这条路径上的权值直接加和,因为重链 dfs 序连续,所以还是线段树区间加和,此时我们节点要到** \(6\) 的父节点 \(1\) 上**,此时发现 \(1,4\) 在同一条重链上,那 \(1,4\) 的权值直接加和即可,还是线段树区间操作,此时路径操作就呼之欲出了!!!

int sumpath(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){//不在同一条链上 
		if(deep[top[x]]<deep[top[y]]){//链顶更深!!! 
			swap(x,y);//交换 
		}
		ans+=query(1,1,n,id[top[x]],id[y]);//链顶到当前节点 
		x=fa[top[x]];//到链顶的父节点上 
	}
	if(deep[x]>deep[y]){//深度小到大的取值加和 
		swap(x,y);
	}
	ans+=query(1,1,n,id[x],id[y]);
	return ans;
}

树链部分lca

可以发现我们不断地在链上向上跳,这于lca的操作非常相似,所以他是可以求 lca 的,我们不断向上跳到同一条链上深度更浅的那个点就是两个节点的 lca。

int sumpath(int x,int y){
	int ans=0;
	while(top[x]!=top[y]){
		if(deep[top[x]]<deep[top[y]]){
			swap(x,y);
		}
		x=fa[top[x]]; 
	}
	if(deep[x]>deep[y]){
		return y;
	}
	return x;
}

边权如何

P4315 月下“毛景树”

如果树的权值到边上成为边权呢,其实还是要转成点权,我们一个点对应一条边,那从根节点出发的边的边权就可以给他的儿子,这样就转成点权了。

当然不可避免的有很多问题,如最后在同一条链上时,要用changesum(1,1,n,id[x]+1,id[y],k);因为根节点的权值是不算在内的。

单边修改时也要查找边对应的点x=id[e[x*2-1].to]<id[e[x<<1].to]?e[x<<1].to:e[x*2-1].to;

例题

P4427 [BJOI2018] 求和

树上差分的思想,预处理前缀和,从根到该节点k次方的总和,答案需要减去 lca 的路径

posted @ 2024-10-29 10:57  sad_lin  阅读(5)  评论(0编辑  收藏  举报