最近公共祖先


最近公共祖先(\(\rm Least\,Common\,Ancestors\)),簡記為 \(\rm LCA\)。顧名思義就是一棵樹中的某兩個節點的公共的祖先中離他們最近,即深度最大的那個

舉個例子:

上圖中 \(8\)\(6\) 的 LCA 就是 \(1\)

那么怎么求 LCA 呢?

1. 向上標記法

思路十分簡單。

我們現在要求 \(\operatorname{LCA(x,y)}\)。首先從 \(x\) 向上到根節點 \(rt\),經過的每一個節點都打上標記。然后從 \(y\) 向上到 \(rt\),遇到的第一個有標記的節點就是 \(LCA\)

代碼實現很簡單,就不給了。

2. 同步前進法

\(\operatorname{LCA(x,y)}\) 的具體方法為:

  1. 先讓 \(x\) 的深度大於或等於 \(y\)。這相當於數學上的一個假設:\(dep(x)\ge dep(y)\)\(dep\) 代表深度)
  2. 不停地將 \(x\) 往上跳,直到 \(x\)\(y\) 深度相等。
  3. 特判:若 \(x=y\)\(LCA\) 就是 \(x\) 了。
  4. 否則在保證 \(fa(x)\ne fa(y)\)\(fa\) 代表父親) 的情況下 \(x\)\(y\) 同時向上跳一格。
  5. \(LCA\) 就是 \(x\) 的父親。

以上面的 \(8\)\(6\) 為例:

  1. \(dep(8)>dep(6)\),無需交換。
  2. \(8\) 跳到 \(4\)\(dep(4)=dep(6)\)
  3. \(4\ne6\)
  4. \(fa(4)=2\ne fa(6)=3\)\(4\) 跳到 \(2\)\(6\) 跳到 \(3\)\(fa(2)=1=fa(3)\),停止。
  5. \(\operatorname{LCA(8,6)}=fa(2)=1\)

在此之前還需 dfs 一遍求出 \(dep\)\(fa\)

void dfs(int u, int father)
{
	fa[u] = father;
	dep[u] = dep[father] + 1; //u深度即father深度加1
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v != father)
		{
			dfs(v, u);
		}
	}
}

int lca(int x, int y)
{
	if (dep[x] < dep[y])
	{
		swap(x, y); //讓x的深度大於或等於y
	}
	while (dep[x] > dep[y])
	{
		x = fa[x]; //向上跳
	}
	if (x == y)
	{
	  	return x; //特判
	}
	while (fa[x] != fa[y])
	{
		x = fa[x]; //同時跳
		y = fa[y];
	}
	return fa[x]; //LCA是父親
}

但以上兩種方法時間復雜度均為 \(\operatorname{O}(nm)\)……

然后洛谷 A 了。

對於 \(100\%\) 的數據,\(N\le500000\)\(M\le500000\)

數據太水了啊啊啊!!!

出數據的真良心。。。

3. 倍增

上面暴力算法顯然過慢,所以我們要使用倍增算法,也是一種空間換時間的策略。

先預處理出 \(lg\) 數組,用來保存 \(\left\lfloor log_2x\right\rfloor\)

for (int i = 2; i <= n; i++)
{
	lg[i] = lg[i >> 1] + 1;
}

\(fa(x)(i)\)\(x\) 的第 \(2^i\) 級祖先,dfs 時要算 \(fa(u)(i)\)

LCA 的第 \(2\) 步,\(x\) 直接向上跳 \(lg(dep(x)-dep(y))\) 格。第 \(4\) 步時,我們遍歷 \(i=lg(dep(x))+1\sim0\),每次 \(x\)\(y\) 同時向上跳 \(2^i\) 格。

void dfs(int u, int father)
{
	fa[u][0] = father; //u的第1級祖先就是父親
	dep[u] = dep[father] + 1;
	for (int i = 1; i <= lg[dep[u]] + 1; i++)
	{
		fa[u][i] = fa[fa[u][i - 1]][i - 1];
	} //算fa(u)(i)
	for (int i = head[u]; i; i = e[i].nxt)
	{
		int v = e[i].to;
		if (v != father)
		{
			dfs(v, u);
		}
	}
}

int lca(int x, int y)
{
	if (dep[x] < dep[y])
	{
		swap(x, y);
	}
	while (dep[x] > dep[y])
	{
		x = fa[x][lg[dep[x] - dep[y]]];
	}
	if (x == y)
	{
	  	return x;
	}
	for (int i = lg[dep[x]] + 1; i >= 0; i--)
	{
		if (fa[x][i] != fa[y][i])
		{
			x = fa[x][i];
			y = fa[y][i];
		}
	}
	return fa[x][0];
}

復雜度分析:

時間 空間
暴力 預處理 \(\operatorname{O}(n)\),每次詢問 \(\operatorname{O}(n)\) \(\operatorname{O}(n)\)
倍增 預處理 \(\operatorname{O}(n\log n)\),每次詢問 \(\operatorname{O}(\log n)\) \(\operatorname{O}(n\log n)\)

P3379 【模板】最近公共祖先(LCA)

暴力:

倍增:

用 LCA 求樹上兩點間的最短距離

還是以這張圖為例主要是我懶

\(8\)\(5\)

預處理出每個點到根節點的距離 \(dis(x)\),這里因為是無權邊,\(dis(x)\) 直接就是 \(dep(x)\) 了。

先用 \(dis(8)\) 加上 \(dis(5)\),此時多加了 \(2\)\(dis(2)\),故 \(8\)\(5\) 的距離就應該是 \(dis(8)+dis(5)-2\times dis(2)\),即樹上 \(x\)\(y\) 兩點間的最短距離為 \(dis(x)+dis(y)-2\times dis(LCA)\)


免責聲明!

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



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