最近公共祖先LCA(链剖)
给定一棵 以
s
s
s 为根节点,共有
n
n
n 个点的树。
有
m
m
m 次查询 每次查询
u
,
v
u ,v
u,v 的最近公共祖先。
算法流程
1
1
1.根据连边的信息建图(邻接表)。代码就不贴了,注意建立双向边。
2
2
2.
d
f
s
1
dfs1
dfs1 ,从给定的起点出发,预处理以下信息:
①
①
①深度:
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
②
②
②父亲:
f
a
[
e
[
i
]
.
t
o
]
=
x
fa[e[i].to] = x
fa[e[i].to]=x
③
③
③大小:
s
i
z
e
[
x
]
+
=
d
f
s
1
(
e
[
i
]
.
t
o
)
size[x] += dfs1(e[i].to)
size[x]+=dfs1(e[i].to)
子树大小无法直接求得,因为该点向下遍历多少点是无法从当前状态求出的。所以我们可以考虑递归回溯来解决。在遍历前自己本身大小为
s
i
z
e
[
x
]
=
1
size[x]=1
size[x]=1 。我们只需要加上遍历到的下一个点的大小即可,所以处理好深度和父亲的信息后,只需回溯
r
e
t
u
r
n
return
return
s
i
z
e
[
x
]
size[x]
size[x]
④
④
④儿子:
i
f
(
s
i
z
e
[
e
[
i
]
.
t
o
]
>
s
i
z
e
[
s
o
n
[
x
]
]
)
s
o
n
[
x
]
=
e
[
i
]
.
t
o
if(size[e[i].to] > size[son[x]]) son[x] = e[i].to
if(size[e[i].to]>size[son[x]])son[x]=e[i].to
链剖法每个点记录的儿子是子树大小最大的点。该记录的最好方式是降低期望查询的时间复杂度,使得查询效率尽可能加快。
dfs1(s);
int dfs1(int x)
{
size[x] = 1;
for(int i = head[x]; i; i = e[i].nxt)
{
if(deep[e[i].to]) continue;
deep[e[i].to] = deep[x] + 1;
fa[e[i].to] = x;
size[x] += dfs1(e[i].to);
if(size[e[i].to] > size[son[x]]) son[x] = e[i].to;
}
return size[x];
}
3.
d
f
s
2
dfs2
dfs2:通过
d
f
s
1
dfs1
dfs1 预处理后,除叶子节点以外,都记录了儿子。若从树根开始按照
s
o
n
[
x
]
son[x]
son[x] 的顺序向下遍历,一定是遍历了一条链,而且沿途所有点的链头都是第一个被遍历的点。
s
o
n
[
x
]
son[x]
son[x] 遍历结束回溯的时候,可以继续找下一个儿子节点继续按这个儿子节点的
s
o
n
[
e
[
i
]
.
t
o
]
son[e[i].to]
son[e[i].to] 又挖出一条链来。
如此流程,原来的树就会被拆成若干条链,每条链的每个点都记录的自己的链头元素。为下一步查询做准备。
dfs2(s, s)
void dfs2(int x, int root)
{
top[x] = root;
if(son[x]) dfs2(son[x], root);
for(int i = head[x]; i; i = e[i].nxt)
if(e[i].to != son[x] && e[i].to != fa[x])
dfs2(e[i].to, e[i].to);
}
4
4
4.查询:对于要查询的
l
c
a
(
u
,
v
)
lca(u, v)
lca(u,v) ,有两种情况:
①
①
①两点在同一条链上,即
t
o
p
[
u
]
=
t
o
p
[
v
]
top[u] = top[v]
top[u]=top[v]。此时深度小的点即为
l
c
a
lca
lca。
②
②
②两点不在同一条链上,即
t
o
p
[
u
]
≠
t
o
p
[
v
]
top[u] \neq top[v]
top[u]=top[v] 。此时 两点中记录的链头的深度较深的点,应跳出此链,到下一条链,然后再次查询新的两点的
l
c
a
lca
lca。即若
d
e
e
p
[
t
o
p
[
v
]
]
>
d
e
e
p
[
t
o
p
[
u
]
]
deep[top[v]] > deep[top[u]]
deep[top[v]]>deep[top[u]] ,
v
=
f
a
[
t
o
p
[
v
]
]
v = fa[top[v]]
v=fa[top[v]]
query_lca(u, v);
int query_lca(int u, int v)
{
while(top[u] != top[v])
{
if(deep[top[u]] > deep [top[v]]) swap(u, v);
v = fa[top[v]];
}
return deep[u] > deep[v] ? v : u;
}
完整代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#include <cstdlib>
using namespace std;
int n, m, s;
int head[1001010],deep[1001010],size[1010100],fa[1010100],son[1010100],top[1101010];
struct list
{
int to,nxt;
}e[1010101];
int read()
{
int rt = 0, in = 1; char ch = getchar();
while(ch < '0' || ch > '9') {if(ch == '-') in = -1; ch = getchar();}
while(ch >= '0' && ch <= '9') {rt = rt * 10 + ch - '0'; ch = getchar();}
return rt * in;
}
void add_edge(int u, int v)
{
e[++head[0]].to = v;
e[head[0]].nxt = head[u];
head[u] = head[0];
}
int dfs1(int x)
{
size[x] = 1;
for(int i = head[x]; i; i = e[i].nxt)
{
if(deep[e[i].to]) continue;
deep[e[i].to] = deep[x] + 1;
fa[e[i].to] = x;
size[x] += dfs1(e[i].to);
if(size[e[i].to] > size[son[x]]) son[x] = e[i].to;
}
return size[x];
}
void dfs2(int x, int root)
{
top[x] = root;
if(son[x]) dfs2(son[x], root);
for(int i = head[x]; i; i = e[i].nxt)
if(e[i].to != son[x] && e[i].to != fa[x])
dfs2(e[i].to, e[i].to);
}
int query_lca(int u, int v)
{
while(top[u] != top[v])
{
if(deep[top[u]] > deep [top[v]]) swap(u, v);
v = fa[top[v]];
}
return deep[u] > deep[v] ? v : u;
}
int main()
{
n = read(), m = read(), s = read();
for(int i = 1; i < n; i++)
{
int u = read(), v = read();
add_edge(u, v), add_edge(v, u);
}
deep[s] = 1;
dfs1(s);
dfs2(s, s);
for(int i = 1; i <= m; i++)
{
int u = read(), v = read();
printf("%d\n",query_lca(u, v));
}
system("pause");
return 0;
}
练习题:
[模版] lca
luogu P3398