一、倍增
对于结点u和v,不妨设deep[u]>deep[v]
(1)、先向上跳u使得它们处于同一深度:deep[v];
(2)、然后再结点u和结点b一起跳,跳它们最近公共祖先。
- 这里所谓“跳”是指往上跳2的次幂的高度。设f[ u ][ i ]表示距结点u高度差为 2^i 的祖先。
- f[ u ][ i ] = f[ f[ u ][ i - 1 ] ][i - 1],其中f[ u ][ 0 ] = fa[ u ].
- 只需要对有根树跑一遍dfs就可以得到f[ u ][ 0 ],然后再根据方程一层层推出其余的次幂祖先。
相关理论
- 我们知道任何一个正整数都可以表示成二进制。根据正整数拆分理论,不管深度差是多少,二者最后一定能处于同一深度。【这个保证了上面所说的第一“跳”一定可以实现】
- 如果u 和 v的最近公共祖先是root,那么root的祖先全部都是u和v的公共祖先,只是不能保证是最近的。【所以第二跳的时候,我们每次往上跳的高度要保证使得f[ u ][ i ] != f[ v ][ i ],这样到最后得到的u和v一定就是它们LCA的两个儿子结点】
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxN = 500005;
inline int read()
{
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -f; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
struct EDGE{
int adj, to;
EDGE(int a = -1, int b = 0): adj(a), to(b) {}
}edge[maxN << 1];
int head[maxN], cnt;
void init()
{
memset(head, -1, sizeof(head));
cnt = 0;
}
void add_edge(int u, int v)
{
edge[cnt] = EDGE(head[u], v);
head[u] = cnt ++ ;
}
int deep[maxN], f[maxN][21], n, m, root;
void dfs(int u, int fa)
{
deep[u] = deep[fa] + 1;
f[u][0] = fa;
for(int i = head[u]; ~i; i = edge[i].adj)
{
if(edge[i].to == fa)
continue;
dfs(edge[i].to, u);
}
}
void LCA_pre()
{
deep[0] = 0;
dfs(root, 0);
for(int i = 1; i <= 20; ++ i )
for(int u = 1; u <= n; ++ u ) //放在内层循环,保证每一层的结点都更新完才更新下一层
f[u][i] = f[f[u][i - 1]][i - 1];
}
int LCA(int u, int v)
{
if(deep[u] < deep[v]) swap(u, v); //保证deep[u] > deep[v]
for(int i = 20; i >= 0; -- i )
{
if(deep[f[u][i]] >= deep[v])
u = f[u][i];
}
if(u == v) return u;
for(int i = 20; i >= 0; -- i )
{
if(f[u][i] != f[v][i])
{
u = f[u][i];
v = f[v][i];
}
}
return f[u][0];
}
int main()
{
init();
n = read(); m = read(); root = read();
for(int i = 0; i < n - 1; ++ i )
{
int u, v; u = read(); v = read();
add_edge(u, v);
add_edge(v, u);
}
LCA_pre();
for(int i = 0; i < m; ++ i )
{
int u, v; u = read(); v = read();
printf("%d\n", LCA(u, v));
}
return 0;
}
二、欧拉序+ST算法
对于一颗有根树,我们求出它的欧拉序vt[ u ],记录下每个结点第一次在欧拉序中出现的位置first[ u ]。
我们可以很容易得到欧拉序结点对应的深度,那么对于结点u和v,它们的LCA就是[ first[u], first[v] ]区间中的最小深度对应的结点。
不懂看下边的栗子啦~
我们知道LCA(D, C) = A。那么通过这个算法的实现求解LCA(D,C)的过程就是:
(1)、first[D] = 3, first[C] = 8.
(2)、确定要查找的欧拉序列:vt [3, 8] = {3, 2, 3, 2, 1, 2}
(3)、找到最小的深度:1
(4)、找到该深度对应的下标:7
(5)、成功找到LCA:vt[7] = A
注意:欧拉序的长度:结点数*2.
因为每个结点出现的次数为:进入该结点的1次+回溯的次数(儿子结点的个数)
我们可以将整体看作进入和回溯各一次,因为如果是叶子结点的回溯就直接是父亲结点的回溯啦~
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <vector>
using namespace std;
typedef long long ll;
const int maxN = 500005;
const int maxM = 1000006;
inline int read()
{
int x = 0, f = 1; char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -f; c = getchar(); }
while(c >= '0' && c <= '9') { x = x * 10 + c - '0'; c = getchar(); }
return x * f;
}
struct EDGE{
int adj, to;
EDGE(int a = -1, int b = 0) : adj(a), to(b) {}
}edge[maxN << 1];
int head[maxN], cnt;
int n, m, root;
int deep[maxN], first[maxN];
int vt[maxM], _min[maxM][25], id[maxM][25];
int tot;
void init()
{
memset(head, -1, sizeof(head));
cnt = 0;
tot = 0;
deep[0] = 0;
}
void add_edge(int u, int v)
{
edge[cnt] = EDGE(head[u], v);
head[u] = cnt ++;
}
void dfs(int u, int fa)
{
deep[u] = deep[fa] + 1;
vt[++ tot] = deep[u];
_min[tot][0] = deep[u];
id[tot][0] = u;
first[u] = tot;
for(int i = head[u]; ~i; i = edge[i].adj)
{
if(edge[i].to == fa) continue;
dfs(edge[i].to, u);
}
vt[++ tot] = deep[fa];
_min[tot][0] = deep[fa];
id[tot][0] = fa;
}
void LCA_pre()
{
for(int j = 1; (1 << j) < tot; ++ j )
{
for(int i = 1; i + (1 << j - 1) < tot; ++ i )
{
if(_min[i][j - 1] <= _min[i + (1 << j - 1)][j - 1])
{
_min[i][j] = _min[i][j - 1];
id[i][j] = id[i][j - 1];
}
else
{
_min[i][j] = _min[i + (1 << j - 1)][j - 1];
id[i][j] = id[i + (1 << j - 1)][j - 1];
}
}
}
}
int LCA(int u, int v)
{
int l = min(first[u], first[v]);
int r = max(first[u], first[v]);
int k = log2(r - l + 1);
if(_min[l][k] <= _min[r - (1 << k) + 1][k])
return id[l][k];
else
return id[r - (1 << k) + 1][k];
}
int main()
{
n = read(); m = read(); root = read();
init();
for(int i = 0; i < n - 1; ++ i )
{
int u, v; u = read(); v = read();
add_edge(u, v);
add_edge(v, u);
}
dfs(root, 0);
LCA_pre();
for(int i = 0; i < m; ++ i )
{
int u, v; u = read(); v = read();
printf("%d\n", LCA(u, v));
}
return 0;
}