最近公共祖先LCA(倍增)
给定一棵 以
s
s
s 为根节点,共有
n
n
n 个点的树。
有
m
m
m 次查询 任意两点
u
,
v
u ,v
u,v 的最近公共祖先
一、前置知识点
1.邻接链表 存图
2.倍增原理 (
2
n
2^n
2n 的预处理应用 )
二、算法流程
1.预处理一:
以
s
s
s 为根节点进行一遍dfs。
处理出:孩子节点
i
i
i 的深度(
d
e
e
p
[
i
]
deep[i]
deep[i]) :
d
e
e
p
[
e
[
i
]
.
t
o
]
=
d
e
e
p
[
x
]
+
1
deep[e[i].to] = deep[x] + 1
deep[e[i].to]=deep[x]+1。
处理出:孩子节点向上跳
2
0
2^0
20所到达的点(即为本身)。:
f
[
e
[
i
]
.
t
o
]
[
0
]
=
x
f[ e[i].to ][0]=x
f[e[i].to][0]=x
f
[
i
]
[
j
]
f[i][j]
f[i][j] 数组表示点
i
i
i 向上跳
2
j
2^j
2j 所到达的点。
2.预处理二:
对 数组
f
[
i
]
[
j
]
f[i][j]
f[i][j] 进行倍增的递推处理。
当
j
=
=
0
j == 0
j==0 时,情况已经在 dfs 中被预处理出来。
考虑当
j
>
0
j > 0
j>0 时,点
i
i
i 向上跳
2
j
2^j
2j所到达的点,即为 点
i
i
i 向上跳
2
j
−
1
2^{j-1}
2j−1所到达的点 再向上跳
2
j
−
1
2^{j-1}
2j−1所到达的点。 因为
2
j
−
1
+
2
j
−
1
=
2
j
2^{j-1} + 2^{j-1} = 2^j
2j−1+2j−1=2j。
所以外层枚举
j
j
j ,内层枚举点
i
i
i 。
易得状态转移方程
f
[
i
]
[
j
]
=
f
[
f
[
i
]
[
j
−
1
]
]
[
j
−
1
]
f[i][j] = f[ f[i][j-1] ][j-1]
f[i][j]=f[f[i][j−1]][j−1]
3.查询:
对于要查询的点
u
,
v
u,v
u,v ,先按深度取深度较深的点,设为点
v
v
v 。
首先算出两点的深度差,设为
d
d
d 。
枚举 次幂
i
i
i 将点
v
v
v 向上跳至 与
v
v
v 同深度。
若此时
u
,
v
u,v
u,v 相等,则返回
u
u
u 。
否则 次幂 从高次 向 低次枚举
i
i
i 。若
u
,
v
u,v
u,v 向上跳
2
i
2^i
2i 所到达的点不相同,则向上跳,然后继续枚举,直到次幂变为0。
三、例题
最近公共祖先LCA模版
货车运输
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
int n,m,s,depth;
int head[1010100],deep[1010100];
int f[505050][21];
struct next_list
{
int to,nxt;
}e[1010100];
void add_edge(int u, int v)
{
e[++head[0]].to = v;
e[head[0]].nxt = head[u];
head[u] = head[0];
}
void dfs(int x)
{
for(int i = head[x]; i; i = e[i].nxt)
{
if(deep[e[i].to]) continue;
f[e[i].to][0] = x;
deep[e[i].to] = deep[x]+1;
dfs(e[i].to);
}
}
int query_lca(int u, int v)
{
if(deep[u] > deep[v]) swap(u,v);
int d = deep[v] - deep[u];
for(int i = 0; i <= depth; i++) if(d & (1<<i)) v = f[v][i];
if(u == v) return v;
for(int i = depth; i >= 0; i--) if(f[v][i] != f[u][i]) v = f[v][i], u = f[u][i];
return f[v][0];
}
int main()
{
scanf("%d%d%d",&n,&m,&s);
depth = log(n) / log(2) + 1;
for(int i = 1; i < n; i++)
{
int u,v;
scanf("%d%d",&u,&v);
add_edge(u,v), add_edge(v,u);
}
deep[s] = 1;
dfs(s);
for(int j = 1; j <= depth; j++)
for(int i = 1; i <= n; i++)
f[i][j] = f[f[i][j-1]][j-1];
for(int i = 1; i <= m; i++)
{
int u,v;
scanf("%d%d",&u,&v);
printf("%d\n",query_lca(u,v));
}
return 0;
}