倍增LCA學習筆記


前言

​ “倍增”,作為一種二進制拆分思想,廣泛用於各中算法,如$ST$表,求解$LCA$等等...今天,我們僅討論用該思想來求解樹上兩個節點的$LCA$(最近公共祖先)

“倍增”是什么東西?

​ 倍增就是“成倍增加”的意思,比如$1$倍增后變成了$2$,$2$倍增后就變成了$4$,$4$變成$8$,以此類推...

實現

一直向上LCA

​ 在講真正的倍增之前,我們先來說說最朴素的$LCA$,對於需要求解的兩個點$(x,y)$,我們最先能想到的方法就是兩個點先到達同一深度,然后一直往上跳父親,知道兩個點跳到同一個點上,這個點就是$LCA$。

int LCA (int x, int y) {
    if (depth[x] < depth[y]) swap(x, y);
    while(depth[x] != depth[y]) x = fa[x];
	while(x != y) x = fa[x], y = fa[y];
	return x;
}

​ 不難發現,這種算法的時間開銷很大,我們想辦法來優化它。

倍增LCA

​ 就如同$ST$表一樣,我們不妨設$f[i][j]$表示樹上編號為$i$的節點向上跳$2^j$個節點后所達到的節點,如同$ST$表的預處理,我們很容易發現如何預處理出這個$f$數組:

f[i][j] = f[f[i][j-1]][j-1];

​ 顯然,$i$往上跳$2{j-1}$次之后再跳$2{j-1}$次之后就相當於$i$往上跳$2^j$次,我們可以借此來優化,利用二進制優化背包的思想那樣,將跳的次數二進制拆分。

​ 於是,我們改寫一下之前的代碼

int LCA (int x, int y) {
    if (depth[x] < depth[y]) swap(x, y);
    for (int i = LogN; i >= 0; --i)
        if (depth[f[x][i]] >= depth[y])
            x = f[x][i];
    if (x == y) return x;
    for (int i = LogN; i >= 0; --i)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return f[x][0];
}

​ 這樣一來,速度就快很多了,由原來的$O(Depth)$變成了現在的$O(log_2(Depth))$

代碼

#include <cstdio>
#include <cstring>
typedef int ll;

const ll N = 5e5 + 10, M = 5e5 + 10, LogN = 25;
ll n, m, s, depth[N], f[N][LogN], a, b, c;
ll from[N], to[M << 1], nxt[M << 1], cnt, tmp, Log[N];
inline void swap (ll &a, ll &b) {tmp = a, a = b, b = tmp;}
//鏈式前向星加邊
void addEdge (ll u, ll v) {
	to[++cnt] = v, nxt[cnt] = from[u], from[u] = cnt;
}
//計算深度&計算祖先
void doit (ll u, ll fa) {
	depth[u] = depth[fa] + 1;
	for (register ll i = 1; i <= Log[n]; ++i) {
		if ((1 << i) >= depth[u]) break;
		f[u][i] =  f[f[u][i - 1]][i - 1];
	}
	for (register ll i = from[u]; i; i = nxt[i]) {
		ll v = to[i];
		if (v == fa) continue;
		f[v][0] = u;
		doit (v, u);
	}
}
//計算LCA
inline ll LCA (ll x, ll y) {
	if (depth[x] < depth[y]) swap(x, y);
    //我們默認x為更深的那個點
	for (register ll i = 0; i <= Log[n]; ++i)
        if (depth[f[x][i]] >= depth[y])
            x = f[x][i];
    //將x跳到和y同一深度上
	if (x == y) return x;
	for (register ll i = Log[n]; i >= 0; --i)
		if (f[x][i] != f[y][i])
			x = f[x][i], y = f[y][i];
    //一起向上跳
	return f[x][0];
    //不難看出,此時兩個點均在其LCA的下方,往上跳一次即可
}

int main () {
	scanf ("%d%d", &n, &m);//n節點數 m詢問次數
	Log[0] = -1;
	for (register ll i = 1, u, v; i < n; ++i) {
		scanf ("%d%d", &u, &v);
		addEdge (u, v); addEdge(v, u);
		Log[i] = Log[i >> 1] + 1;
	}
	Log[n] = Log[n >> 1] + 1;
	doit (1, 0);
	while (m--) {
		scanf ("%d%d", &a, &b);
		printf ("%d\n", LCA(a, b)));
	}
	return 0;
} 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM