【算法-树论】树上最近公共祖先(LCA)
引入
观看例题,观察到直接向上爬的朴素 LCA 最差复杂度需要 \(2n\),会 T。
需要效率更高的方法找出公共祖先。
定义
找到两个不同的点的最近公共祖先的算法。
最近公共祖先:同时为两个点的祖先且深度尽可能大。
\(dep_x\) 为点 \(x\) 的深度。
具体实现
- 倍增 LCA
对于每个点 \(x\) 进行祖先记录,记录方式为:记录第 \(2^0, 2^1, 2^2 \dots 2^{\log\{dep_x\}}\) 个祖先。
对于寻找点 \(x\) 和点 \(y\ (x \neq y)\) 的最近公共祖先,进行以下操作:
不妨设 \(dep_x \geq dep_y\)。
然后将点 \(x\) 的深度上调到不小于 \(dep_y\)。
如果 \(x = y\),结束。
否则将点 \(x\) 和点 \(y\) 同步上调同样的最大深度(前提是上调后不相等)。
易证此时 \(x\) 的父亲就是原 \(x\) 和 \(y\) 的最近公共祖先。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define pii pair<int, int>
#define mp(x, y) make_pair(x, y)
const int man = 5e5+10, mam = 5e5+10;
class Graph {
public:
int hed[man], len = 0;
int nxt[mam<<1], ver[mam<<1];
void Ins (int u, int v) {
ver[++len] = v;
nxt[len] = hed[u];
hed[u] = len;
return ;
}
void DIns (int u, int v) {
Ins(u, v), Ins(v, u);
return ;
}
} G;
int n, m, s;
int _log[man], dep[man];
int anc[man][50];
void dfs (int) ;
int query (int, int) ;
signed main(void) {
#ifndef ONLINE_JUDGE
freopen("test.in", "r", stdin);
freopen("test.out", "w", stdout);
#endif
scanf("%d%d%d", &n, &m, &s);
for (int i = 1; i <= n; ++ i) _log[i] = _log[i-1]+(1<<_log[i-1]==i);
for (int u, v, i = 1; i < n; ++ i) scanf("%d%d", &u, &v), G.DIns(u, v);
dep[s] = 1;
dfs(s);
for (int x, y, i = 1; i <= m; ++ i) {
scanf("%d%d", &x, &y);
printf("%d\n", query(x, y));
}
#ifndef ONLINE_JUDGE
fclose(stdin);
fclose(stdout);
#endif
return 0;
}
// ---
void dfs (int x) {
for (int i = 1; i <= _log[dep[x]]; ++ i)
anc[x][i] = anc[anc[x][i-1]][i-1];
for (int i = G.hed[x]; i; i = G.nxt[i]) {
int v = G.ver[i];
if (dep[v]) continue;
dep[v] = dep[x]+1;
anc[v][0] = x;
dfs(v);
} return ;
}
int query (int x, int y) {
if (dep[x] < dep[y]) swap(x, y); // need x deeper than y
while (dep[x] > dep[y]) {
int k = _log[dep[x]-dep[y]];
x = anc[x][k-1];
} if (x == y) return x;
for (int i = _log[dep[x]]-1; i >= 0; -- i)
if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i];
return anc[x][0];
}

浙公网安备 33010602011771号