给定一棵 以 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
#include
#include
#include
#include
#include
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