【模版】最近公共祖先LCA(链剖)


最近公共祖先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

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值