树剖Ⅱ-LCA模板:对树剖的再认识

首先我们已经明确,树链剖分是将树上操作转化为线性操作的高端暴力。
树转链的操作,我们可以通过两遍dfs来实现。然后我们利用线段树或树状数组之类的来维护第二遍dfs序即可。
鉴于我已经两年每写过模拟之外的东西了(今天上午复习了SPFA,这个另算),这次的重点放在了对dfs的理解上。

如何存储树

一开始想用结构体,后来又想到邻接表,但儿子的个数不定而且用存图的方法存树未免有些奇怪~~(虽然树也是图的一种)~~ 所以最终敲定为vector储存儿子,fa数组储存父亲。
鉴于我们读入时不知道父子关系,便先在储存儿子的vector中都存着,在dfs中解决父子关系问题。

第一遍dfs之谁爹谁儿子

这个问题困扰了我好久。一开始想特判根,因为根的vector里一定全是儿子,但后来发现其他儿子进了dfs后就不好处理了,于是想到,先在dfs之外预处理好根的情况,进了dfs以后,根和叶子就可以一视同仁了。
那么我们的dfs肯定先从根开始。进入第一层dfs后我们遍历根的vector(儿子们),一定全是儿子。这时候直接让儿子们的fa为根,然后进儿子们的dfs就好。
进入到儿子们的dfs后,这些儿子们肯定都是有父亲的,于是在遍历儿子们的vector时,记得把fa剔除掉就好了。
写一段伪代码吧

for(register int i = 0; i < len; i++)
	if(v != fa[x])
	{
		fa[v] = x;
		dfs(v);
	}

可以看到,对于任意节点x,它的所有儿子与它的父亲都在vector里,而v不是x的父亲时,x就一定是v的父亲,然后我们这个有父亲的v可以继续快乐的dfs下去了~耶!

第一遍dfs之谁是重儿子

第一遍dfs的核心任务就是统计儿子的个数并找出重儿子。那么如何实现统计儿子的个数呢?
我们不妨假设,当我们对某个节点v的dfs后,我们已经知道了这个节点v的子树中的全部节点数sz[v],那么对于这个节点的父亲x来说,把所有的sz[v]加起来,再加上自己,就是节点x的子树中的全部节点数sz[x]。这样同时就实现了完成dfs并统计儿子个数的任务。
伪代码如下

sz[x]++;
for(register int i = 0; i < len; i++)
{
	dfs(v);
	sz[x]+=sz[v];
}

第一个++是自己,而且对于叶子来说它也没儿子啊所以不会进入for循环的。
既然我们实现了“完成dfs的同时也把儿子个数给统计出来了”这样的任务,寻找重儿子的工作也就迎刃而解了,加一个if判断一下就OK了。

if(sz[v] > sz[hs[x]]) 
	hs[x] = v;

第一遍dfs之还要干点啥

作为一个LCA题,还是维护一下深度的好,毕竟,这次我们的dfs序连维护都不用维护的啊!!!跑两遍就完事了orz

第二遍dfs之top有啥用

我们第二遍dfs之后得到的就是所需树链了。对于链上的点,我们需要一个top数组稍加维护。这个top的意义何在?如同其字面意思,top记录了每一个树链的顶端。当我们得到一串长长的dfs序后,我们并不清楚其中链的关系,而top则为我们指明了方向。对于一条链来说,深度最浅的点自然是整条链的top,而知道了一个点的top,也就知道了它属于哪条链。
灵魂画师再度上线~
我觉得图里降低很明白了

LCA之如何实现

对于任意两个节点x,y,如何找到他们的公共祖先呢?
首先是最简单的情况,x,y在同一条链上,自然深度浅的是祖先。
如果不在同一条链上呢?
我们就想到,要是他们的某个祖先在同一条链上,就那公共祖先就找到了。
那找它那个祖先呢?用fa一个个往上找自然是太慢了,如果用top显然会快一点,例如上图的A节点与2节点,用fa往上找和用top往上找显然时间复杂度差了太多。但这也不是最优,毕竟我们知道了x,y不在同一条链上,那么和x同一条链的top自然也和y不在同一条链上,索性直接跳到fa[top[x]]上更方便。
这样我们也理解了为什么根的父亲是根自己。(如果年代久远又理解不了了就再去康康上图的A节点与2节点——致未来的自己)
代码实现

inline int LCA(int x, int y)
{
	while(top[x] != top[y])
	{
		if(dep[top[x]] > dep[top[y]]) swap(x, y);
		y = fa[top[y]];
	}
	if(dep[x] > dep[y]) return y;
	return x;
}

解决了上述问题,最后就是全部的代码了~
鼓掌~
撒花~

#include <cstdio>
#include <vector>
using namespace std;

inline long long read()
{
	char ch = getchar();
	long long ans = 0;
	int i = 1;
	while(ch != '-' && (ch < '0' || ch > '9')) ch = getchar();
	if(ch == '-') i = -1, ch = getchar();
	while(ch >= '0' && ch <= '9')
	{
		ans = ans * 10 + ch - '0';
		ch = getchar();
	}
	return i * ans;
}

int N, M, S;
int fa[500100], hs[500100], top[500100], sz[500100], dep[500100];

vector <int> son[500100];

#define v son[x][i]

inline void dfs(int x)
{
	sz[x]++;
	int len = son[x].size();
	for(register int i = 0; i < len; i++)
		if(v != fa[x])
		{
			fa[v] = x;
			dep[v] = dep[x] + 1;
			dfs(v);
			sz[x] += sz[v];
			if(sz[v] > sz[hs[x]])
				hs[x] = v;
		}
	return ;
}

inline void DFS(int x, int r)
{
	top[x] = r;
	if(hs[x])
		DFS(hs[x], r);
	int len = son[x].size();
	for(register int i = 0; i < len; i++)
		if(v != fa[x] && v != hs[x])
			DFS(v, v);
	return ;
}

inline int LCA(int x, int y)
{
	while(top[x] != top[y])
	{
		if(dep[top[x]] > dep[top[y]]) swap(x, y);
		y = fa[top[y]];
	}
	if(dep[x] > dep[y]) return y;
	return x;
}

int main()
{
	N = read();
	M = read();
	S = read();
	register int a, b;
	for(register int i = 1; i < N; i++)
	{
		a = read();
		b = read();
		son[a].push_back(b);
		son[b].push_back(a);
	}
	fa[S] = S;
	dfs(S);
	DFS(S, S);
	for(register int i = 1; i <= M; i++)
	{
		a = read();
		b = read();
		printf("%d\n", LCA(a, b));
	}
	return 0;
}

其实只有90行,挺短的,不是么~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章