樹剖Ⅱ-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行,挺短的,不是麼~

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