LCA

有根树中

把点本身也称为它的祖先

方法1:向上标记法(不常用)

从一个点开始向上遍历标记,再从第二个开始向上遍历标记,第一个遍历到的标记的点是LCA

方法2:倍增

预处理fa[i,j]表示从i开始,向上走2^j步所能走到的点

\[0<=j<=log(n) \]

\[f[i,j]= f[ f[i,j-1],j-1] \]

先跳一半,再跳一半

\[depth[i] \]

表示深度

Step1先将两个点跳到同一层

Step2让两个点同时往上跳

从大到小依次枚举2^k步,直到跳到同一层/两个点跳到了最近公共祖先的下一层(为何不是跳到最近公共祖先?),因为两个点跳了若干步后相同,这并不能说明是最近公共祖先。

方法3:并查集离线求lca

基于dfs

复杂度:O(m+n)

把点分成三大类:

1.已经遍历过,且回溯过的的点

2.正在搜索的分支

3.还未搜索到的点

例题:祖孙询问

Code:

#include<bits/stdc++.h>
#define LL long long
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 4e4 + 10, M = 2 * N;
int n, m;
int root;
int h[N], e[M], ne[M], idx;
void add(int a, int b) {
	ne[idx] = h[a], e[idx] = b, h[a] = idx++;
}

int type[N];
int p[N];
int find(int x) {
	if (p[x] != x)p[x] = find(p[x]);
	return p[x];
}

vector<int> query[N],id[N];
PII q[N];
int res[M];
//!!!tarjan算法模板
void tarjan(int u, int pa) {
	type[u] = 1;
	for (int i = h[u]; ~i; i = ne[i]) {
		int k = e[i];
		if (k == pa)continue;//注意p别重名
		tarjan(k,u);
		p[k] = u;//将当前节点的所有子树合并到父节点
	}
	for (int i = 0; i < query[u].size(); i++) {
		int j = query[u][i];
		if (type[j] == 2) {
			res[id[u][i]] = find(j);
		}
	}
	type[u] = 2;
}

int main() {
	//freopen("in.in", "r", stdin);
	memset(h, -1, sizeof  h);
	for (int i = 1; i < N ; i++)p[i] = i;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		int a, b;
		cin >> a >> b;
		if (b == -1)root = a;
		else
		add(a, b),add(b,a);
	}
	cin >> m;
    //离线存
	for(int i=1;i<=m;i++) {
		int x, y;
		cin >> x >> y;
		query[x].push_back(y);
		query[y].push_back(x);
		id[x].push_back(i);
		id[y].push_back(i);
		q[i] = { x,y };
	}
	tarjan(root,-1);
	for (int i = 1; i <= m; i++) {
		int lca = res[i];
		int x = q[i].first, y = q[i].second;
		if (lca == x)puts("1");
		else if (lca == y)puts("2");
		else puts("0");
	}

}

方法4(不常用):RMQ

dfs序遍历后,对于询问

\[lca(x,y) \]

找dfs序中x和y之间的深度最小的点

即转化为一个数组中求区间的最值问题

可以用线段树或者RMQ求解