LCA及应用

痴心易碎 提交于 2020-01-30 15:25:34

定义:

  最近公共祖先简称 LCA(Lowest Common Ancestor)。两个节点的最近公共祖先,就是这两个点的公共祖先里面,离根最远的那个。

性质:

1.LCA(u)=uLCA(u)=u;
2. uuvv的祖先,当且仅当LCA(u,v)=uLCA(u,v)=u
3. 如果uu不为vv的祖先并且vv不为uu的祖先,那么 u,vu,v分别处于LCA(u,v)LCA(u,v)的两棵不同子树中;
4. 前序遍历中, LCA(S)LCA(S) 出现在所有 SS 中元素之前,后序遍历中 LCA(S)LCA(S) 则出现在所有 SS 中元素之后;
5. 两点的最近公共祖先必定处在树上两点间的最短路上;
6. d(u,v)=h(u)+h(v)2h(LCA(u,v))d(u,v) = h(u) + h (v) - 2 h(LCA(u,v)),其中 dd 是树上两点间的距离,hh 代表某点到树根的距离。

算法实现:

1.朴素算法:

  让 uuvv 中深度深的点先走 depth[v]depth[u]|depth[v]-depth[u]| 步,然后两个点同时向上走,直到走到同一个节点。

复杂度:

O(n)O(n)

2.倍增(基于二分搜索):

  朴素算法的改进算法。
  通过预处理 pre[k][i]pre[k][i] 数组,游标可以快速移动,大幅减少了游标跳转次数。pre[k][i]pre[k][i] 表示点 ii 的第 2k2^k 个祖先。pre[k][i]pre[k][i] 数组可以通过 dfsdfs 预处理出来。

复杂度:

每次询问的复杂度为 O(logn)O(logn),预处理 pre[k][i]pre[k][i] 数组的复杂度为 O(nlogn)O(nlogn)

主要优化:

(1).(1).对于深度不同的两个点 uuvv ,其深度的差值为 d=depth[u]depth[v]d=|depth[u]-depth[v]| ,然后对其进行二进制拆分,借助 pre[k][i]pre[k][i] 数组,将差值消除,使得两个点的深度相同。
(2).(2).将两个点调整至相同位置之后,与朴素算法相同,两个点同时向上跳。具体实现:从最大可能的 kk开始尝试,直到 00 (包括 00 )。如果 pre[k][u]!=pre[k][v]pre[k][u]!=pre[k][v] ,那么 u=pre[k][u],v=pre[k][v]u=pre[k][u],v=pre[k][v] 。否则继续尝试。
注意不能从低位到高位继续尝试,会把 lcalca 跳过。
该技巧在 LCALCA 以外的地方也可使用。

具体代码实现:
const int N=1e4+5;
const int maxk=20;//最大可能k值
vector<int>pic[N];//存图
int depth[N],pre[maxk][N];//点的深度,及预处理的2^k表
int root,n;//根节点,节点数
void dfs(int v,int p,int d)//(当前点,其父亲节点,其深度)
{//处理出各点的深度和父亲
    depth[v]=d;
    pre[0][v]=p;
    for(int i=0;i<pic[v].size();i++)
    {
        if(pic[v][i]!=p)
            dfs(pic[v][i],v,d+1);
    }
}
void init()
{
    //预处理出depth和pre[0][i];
    dfs(root,-1,0);//根节点父亲=-1
    //预处理出全部的pre
    for(int k=0;k+1<maxk;k++)
    {
        for(int i=1;i<=n;i++)
        {
            if(pre[k][i]==-1)
                pre[k+1][i]=-1;
            else
                pre[k+1][i]=pre[k][pre[k][i]];
        }
    }
}
int lca(int u,int v)
{
    if(depth[u]>depth[v])//保持u的深度小于v
        swap(u,v);
    //消除深度差
    for(int k=0;k<maxk;k++)//枚举差值的二进制位,从低到高,从高到低均可
    {
        if((depth[v]-depth[u])>>k&1)
            v=pre[k][v];//深度差-=2^k
    }
    if(u==v)
        return u;
    //二分搜索计算lca
    for(int k=maxk-1;k>=0;k--)//只能从高到低,从低到高会把lca跳过
    {
        if(pre[k][u]!=pre[k][v])
        {
            u=pre[k][u];
            v=pre[k][v];
        }
    }
    return pre[0][u];
}

例题:
Nearest Common Ancestors POJ - 1330【模板题】

#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
const int N=1e4+5;
vector<int>pic[N];//存图
int depth[N],pre[20][N],degree[N];//点的深度,及预处理的2^k表
int root,maxk=20,n;//根节点,最大可能k值,节点数
void dfs(int v,int p,int d)//处理出各点的深度和父亲
{
    depth[v]=d;
    pre[0][v]=p;
    for(int i=0;i<pic[v].size();i++)
    {
        if(pic[v][i]!=p)
            dfs(pic[v][i],v,d+1);
    }
}
void init()
{
    //预处理出depth和pre[0][i];
    dfs(root,-1,0);
    //预处理出全部的pre
    for(int k=0;k+1<maxk;k++)
    {
        for(int i=1;i<=n;i++)
        {
            if(pre[k][i]==-1)
                pre[k+1][i]=-1;
            else
                pre[k+1][i]=pre[k][pre[k][i]];
        }
    }
}
int lca(int u,int v)
{
    if(depth[u]>depth[v])//保持u的深度小于v
        swap(u,v);
    //消除深度差
    for(int k=0;k<maxk;k++)//枚举差值的二进制位,从低到高,从高到低均可
    {
        if((depth[v]-depth[u])>>k&1)
            v=pre[k][v];//深度差-=2^k
    }
    if(u==v)
        return u;
    //二分搜索计算lca
    for(int k=maxk-1;k>=0;k--)//只能从高到低,从低到高会把lca跳过
    {
        if(pre[k][u]!=pre[k][v])
        {
            u=pre[k][u];
            v=pre[k][v];
        }
    }
    return pre[0][u];
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d",&n);
        int u,v;
        memset(degree,0,sizeof(degree));
        for(int i=1;i<=n;i++)
            pic[i].clear();
        for(int i=1;i<n;i++)
        {
            scanf("%d%d",&u,&v);
            pic[u].push_back(v);
            degree[v]++;
        }
        scanf("%d%d",&u,&v);
        for(int i=1;i<=n;i++)
        {
            if(degree[i]==0)
            {
                root=i;
                break;
            }
        }
        init();
        printf("%d\n",lca(u,v));
    }
    return 0;
}

3.Tarjan算法(离线):
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!