1. 程式人生 > >LCA線上演算法-基於ST表

LCA線上演算法-基於ST表

LCA演算法-ST線上演算法

轉自: https://blog.csdn.net/qq_35935435/article/details/54916022

這篇文章講LCA演算法(Least Common Ancestor)。

LCA:顧名思義,指在一棵有根樹中,距離兩個結點u和v最近的公共祖先(換句話說,是離根節點最遠的公共祖先)。 
LCA演算法:對於該問題,最容易想到的演算法是分別從節點u和v回溯到根節點,獲取u和v到根節點的路徑P1,P2,其中P1和P2可以看成兩條單鏈表,這就轉換成常見問題:判斷兩個單鏈表是否相交,如果相交,給出相交的第一個點。該演算法總的複雜度是O(n)(其中n是樹節點個數)。但當資料量非常大且查詢很頻繁時,該演算法無法在有效時間內查詢出正解。所以本篇文章將介紹一種比較高效的演算法解決該問題。線上演算法(DFS+ST)

 
①DFS 
舉個例子。 
例子圖片 
假設遍歷順序從右至左,則DFS遍歷可得。 
DFS遍歷序列F_____________1 3 5 7 5 6 5 3 4 3 1 2 
深度序列deep______________1 2 3 4 3 4 3 2 3 2 1 2 
結點首次出現位置first ____1 12 2 9 3 6 4 
對於查詢兩個結點的LCA就是各自首次出現位置間深度最小的結點。於是可以轉化為RMQ問題,在一段區間中尋找最小值。所以再用ST演算法解決該問題。 
根據例題,如果詢問LCA(4,7),就相當於RMQ(4,7)。first[4]=9,first[7]=4。在深度序列中區間(4,9)是4 3 4 3 2 3,而最近公共祖先的深度就是這段區間中最小的2。再將深度代入遍歷序列得到LCA(4,7)=3。 
②ST
 
令F[i,j]為從下標i開始,長度為2^j的元素的最小值。那麼狀態轉移方程就是F[i,j]=min(F[i,j-1],F[i+2^(j-1),j-1])。這個式子在這裡有詳細解釋哦! 
③查詢 
假如要查詢[m,n]的最小值,那麼先求出一個最大的k。使k滿足2^k<=(n-m+1)。於是我們可以將[m,n]分成兩個(部分重疊的)長度為2^k的區間:[m,m+2^k-1],[n-2^k+1,n];F[m,k]為F[m,m+2^k-1]的最小值,F[n-2^k+1,k]是[n-2^k+1,n]的最小值。狀態轉移方程:RMQ(i,j)=min(F[m,k],F[n-2^k+1,k]);
 
接下來看一下例題&程式碼。

LZOI2225 最近公共祖先

題目描述 
如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。 
輸入 
第一行包含三個正整數n, q, s,分別表示樹的結點個數、詢問的個數和樹根結點的序號。 
接下來n - 1行每行包含兩個正整數u, v,表示u結點和v結點只見有一條直接連線的邊(資料保證可以構成樹)。 
接下來q行每行包含兩個正整數a、b,表示詢問a結點和b結點的最近公共祖先。 
輸出 
輸出包含q行,每行包含一個正整數,依次為每一個詢問的結果。

CODE: 

#include<bits/stdc++.h>
using namespace std;
int n, q, root, u, v, a, b, F[50001], f[50001][21], first[50001], deep[50001], head[50001], tot;
bool vis[50001];
struct EDG
{
	int u, v, next;
}edg[10000];
void DFS(int u, int dep)
{
	vis[u] = 1;
	F[++tot] = u;
	first[u] = tot;
	deep[tot] = dep;
	for (int i = head[u]; i != 0; i = edg[i].next)
		if (!vis[edg[i].v])
		{
			DFS(edg[i].v, dep + 1);
			F[++tot] = u;
			deep[tot] = dep;
		}
}

int RQ(int i, int j)
{
	return deep[i] <= deep[j] ? i : j;
}
void ST(int N) {
	int temp = (int)(log((double)N) / log(2.0));
	for (int i = 0; i < N; i++) f[0][i] = i;
	for (int i = 1; i <= temp; ++i)
		for (int j = 0; j + (1 << i) - 2 < N; ++j)
			f[i][j] = RQ(f[i - 1][j], f[i - 1][j + (1 << (i - 1))]);
}
int query(int L, int R)
{
	int k = (int)(log((double)(R - L + 1)) / log(2.0));
	return RQ(f[k][L], f[k][R - (1 << k) + 1]);
}


int LCA(int u, int v)
{
	int x = first[u], y = first[v];
	if (x > y)swap(x, y);
	int res = query(x, y);
	return F[res];
}
int main()
{
	cin >> n >> q >> root;
	for (int i = 2; i <2*n; )
	{
		cin >> u >> v;
		edg[i] = EDG{ u,v,head[u] };
		head[u] = i++;
		edg[i] = EDG{ v,u,head[v] };
		head[v] = i++;
	}
	DFS(root, 1);
	ST(tot);
	for (int i = 1; i <= q; i++)
	{
		cin >> a >> b;
		cout << LCA(a, b) << endl;
	}
	return 0;
}