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求解
浙公网安备 33010602011771号