首页 > 其他 > 详细

树上问题

时间:2020-07-12 09:23:21      阅读:53      评论:0      收藏:0      [点我收藏+]

主要是根据学长的课件来透彻的.

树链剖分

所谓树链剖分,就是将树上的边进行划分。

树链剖分有重链剖分,长链剖分,实链剖分等等。

长链剖分是用来\(O(1)\)\(k\)级祖先的,和优化一些树形DP,具体地来说是一些跟深度有关的DP。

实链剖分是我们常说的LCT(Link-Cut-Tree)。

本文介绍的就是重链剖分。


重链剖分

既然是重链剖分,那么一定有重链和轻链,但是我们怎么来划分轻重链呢?

我们定义:一个节点的所有子节点中\(size\)最大的那个节点为重儿子;那么这两个节点之间所连的边为一条重边。

这样的话我们会得到一些性质。

性质1:从根节点到叶子节点的路径上,跳重链的次数不会超过\(O(logn)\),从叶子节点到根节点,也成立

证明:我们考虑,什么时候会跳重链,一定是当他需要向轻儿子走的时候废话。那么轻儿子的\(size\),一定小于他父亲节点

\(\frac{1}{2}\),那么不断走轻儿子,一直乘\(\frac{1}{2}\),只需要跳\(\log n\)次,就到\(1\)了,也就是叶子节点。

代码:

int fa[maxn], dep[maxn], size[maxn], height_son[maxn];
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
	size[now] = 1;
	for(int i = 0; i < v[now].size(); i ++){
		int to = v[now][i];
		if(dep[to]) continue;
		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
		fa[to] = now;
		bulid_poutree(to);
		size[now] += size[to];
		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
	}
}

树剖求LCA

我们考虑,如果两个点在同一条重链上,那么肯定是深度小的是LCA。

但是如果不在呢?

我们是不是可以像倍增一下跳(也仅仅是像),跳重链,向上跳,然后一直跳到两个点在同一条重链上,再像刚才一样处理。

让谁跳呢,深度大的?

不不不,是\(top\)深度大的跳。

因为,既然两个点不在同一条重链上,那么显然,他们的\(top\),也不在同一条重链上。

那么类似于倍增,让\(top\)\(fa\),直到跳到同一条重链上。

深度小的是LCA。

本人还不透彻,不如去写倍增


升华

前置芝士:线段树。

先上一道例题来透彻一下树链剖分吧。

好像不只是跳重链那么简单。

在这里,我们引入一个叫做\(dfs\)序的概念。

\(dfs\)序:我们在遍历整棵树时,每个节点被遍历到的时间戳(即这个节点是第几个被遍历到的)。

那么我们显然可以发现一颗子树内的\(dfs\)序是连续的,而且一条链上的\(dfs\)序也是连续的。

那么我们在询问一条路径时,就可以把这条路径分成好几条链,我们对于每条链分别统计即可。

那么我们怎么才能维护每条链的信息呢?

注意:一条链上的\(dfs\)序是连续的,那么问题转化为,如何维护一个区间的信息?

当然是线段树啊。

什么区间和,区间最值,区间覆盖,线段树简直不能再合适了。

那么是不是这道题就做完了呢?

那么我就可以保证我们树链剖分的时间复杂度了\(O(n\log^2n)\)

int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn];
/*其中dfn为该点所对应的dfs序,id为该点的dfs序所对应的自己原来的编号(不确定对不对qaq)),top为这条重链上最高的点(这个是对的qwq)*/
vector<int> v[maxn];
inline void add(int x, int y){return (void)(v[x].push_back(y));}
void bulid_poutree(int now){
	size[now] = 1;
	for(int i = 0; i < v[now].size(); i ++){
		int to = v[now][i];
		if(dep[to]) continue;
		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
		fa[to] = now;
		bulid_poutree(to);
		size[now] += size[to];
		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
	}
}

void dfs(int now, int topfa){ //找到点对应的dfs序
	top[now] = topfa;
	dfn[now] = ++cnt;
	id[cnt] = now;
	if(height_son[now]) dfs(height_son[now], topfa); //先走重链
	for(int i = 0; i < v[now].size(); i ++){//走轻链
	        int to = v[now][i];
		if(fa[now] == to or height_son[now] == to) continue;
		dfs(to,to);
	}
}

找到dfs序以后我们就把这个问题美滋滋的转化为了区间问题

树上问题

原文:https://www.cnblogs.com/Vanyun/p/13286973.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!