LCA学习笔记

写在前面

目录

一、LCA的定义

二、暴力法求LCA

三、倍增法求LCA

四、树链剖分求LCA

五、LCA典型例题

题目完成度


 

 

一、LCA的定义

LCA指的是最近公共祖先。具体地,给定一棵有根树,若结点z既是结点x的祖先,又是结点y的祖先,则称z是x,y的公共祖先。在x,y的公共祖先中,深度最大的一个结点称为x,y的最近公共祖先,记为LCA(x,y)

go back

 

 


 

 

、暴力法求LCA

暴力法,顾名思义,非常暴力,这里简单介绍一下

先DFS一遍找出每个点的深度,然后先从深度大的往上跳,跳到x,y两个点深度相同。

如果发现此时x和y是同一个结点,那么原本深度小的那个结点就是两个点的最近公共祖先。

如果不是同一个点,那么就两个点一起同时往上跳,直到发现两个点相同,则此时到达的这个结点为x,y两点的最近公共祖先。

484很简单?QWQ

那我等下放个代码,就酱紫吧

int LCA(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);//默认x深度大于y
    while(dep[x]>dep[y]) x=fa[x];//x每次变成自己的父结点,即往上跳一步
    if(x==y) return y;
//如果此时两个结点相同,那么原本深度较小的结点就是LCA
    while(x!=y)
        x=fa[x],y=fa[y];//两个点同时一步步往上跳
    return x;//此时两个节点相同,都是LCA
}

其实还有一种暴力的方法

就是先让其中一个点一路跳到根结点,标记经过的结点,然后另一个点也往上跳,遇到的第一个标记了的结点就是LCA

同样放下代码

int LCA(int x,int y){
    while(x!=root){//root为根结点
        x=fa[x];
        visit[x]=1;//标记
    }
    while(!visit[y])//如果遇到被标记了的就找到了LCA
        y=fa[y];
    return y;
}

go back

 


 

三、倍增法求LCA

这是一个非常重要的算法啦!一定要记住QAQ

我来讲一讲啦

【预处理】

设f[x][k]表示x结点的2k辈祖先,即从x向根结点走2k步到达的结点

如果该结点不存在,则令f[x][k]=0,f[x][0]就是x的父亲结点

因为2k=2k-1+2k-1,所以对于1≤k≤logn,有f[x][k]=f[f[x][k-1]][k-1]

预处理部分的时间复杂度为O(nlongn)

void ready(int x,int father){
    dep[x]=dep[father]+1;//计算深度
    go(i,0,19)//预处理f数组
        f[x][i+1]=f[f[x][i]][i];
    for(int i=head[x];i;i=next[i]){
        int y=to[i];//用链式前向星存边
        if(y==father) continue;
        f[y][0]=x;
        ready(y,x);
    }
}

【查找LCA】

预处理过后可以对多个x,y查找LCA,每次的时间复杂度均为O(nlogn)

具体操作如下:

1.假设dep[x]≥dep[y],如果不成立可以交换

2.用二进制拆分思想把x上调到与y同一深度

其实就是依次尝试让x向上跳k=2logn...21,20,若到达的结点比y深,则令x=f[x][k]

3.和上面说的一样,若此时x与y相等,则y就是LCA

4.若此时x≠y,那么x和y同时往上跳,同样用二进制拆分思想,把x和y同时向上跳,并保持深度一致且不相会

与上面调整x时一样,让x和y尝试向上走k=2logn...21,20步,若f[x][k]≠f[y][k](即两点不相会),则令x=f[x][k],y=f[y][k]

5.结束时x和y必然只差一步就相会了,所以他们的父节点f[x][0]就是LCA啦!QWQ

int LCA(int x,int y){
    if(dep[x]<dep[y]) swap(x,y);
    back(i,20,0){
        if(dep[f[x][i]]>=dep[y]) x=f[x][i];
//只要x的深度没有比y小就可以继续跳
        if(x==y) return x;
    }
    back(i,20,0){
        if(f[x][i]!=f[y][i])//跳的过程中要保证两点不相会
            x=f[x][i],y=f[y][i];
    }
    return f[x][0];
}

最后再放一个完整版代码吧QWQ

 1 #include<bits/stdc++.h>
 2 #define go(i,a,b) for(register int i=a;i<=b;i++)
 3 #define back(i,a,b) for(register int i=a;i>=b;i--)
 4 using namespace std;
 5 const int N=100002;
 6 int n,m;
 7 int dep[N],head[N];
 8 int f[N][20];
 9 int next[N*2],to[N*2],num=0;
10 int last=0;
11 int fr(){
12     int w=0,q=1;
13     char ch=getchar();
14     while(ch<'0'||ch>'9'){
15         if(ch=='-') q=-1;
16         ch=getchar();
17     }
18     while(ch<='9'&&ch>='0')
19         w=(w<<1)+(w<<3)+ch-'0',ch=getchar();
20     return w*q;
21 }
22 void ready(int x,int father){
23     dep[x]=dep[father]+1;
24     go(i,0,18)
25         f[x][i+1]=f[f[x][i]][i];
26     for(int i=head[x];i;i=next[i]){
27         int y=to[i];
28         if(y==father) continue;
29         f[y][0]=x;
30         ready(y,x);
31     }
32 }
33 int LCA(int x,int y){
34     if(dep[x]<dep[y]) swap(x,y);
35     back(i,19,0){
36         if(dep[f[x][i]]>=dep[y]) x=f[x][i];
37         if(x==y) return x;
38     }
39     back(i,19,0){
40         if(f[x][i]!=f[y][i])
41             x=f[x][i],y=f[y][i];
42     }
43     return f[x][0];
44 }
45 int main(){
46     n=fr();
47     int root,father;
48     go(i,1,n){
49         father=fr();
50         if(father==0) root=i;
51         next[++num]=head[father];
52         to[num]=i;
53         head[father]=num;
54         next[++num]=head[i];
55         to[num]=father;
56         head[i]=num;
57     }
58     ready(root,0);
59     m=fr();
60     while(m--){
61         int x=fr(),y=fr();
62         last=LCA(x,y);
63         printf("%d\n",last);
64     }
65     return 0;
66 }
代码戳这里

go back

 


四、树链剖分求LCA

 咕咕咕咕咕

go back


五、LCA典型例题

 咕咕咕咕

go back

posted @ 2019-02-14 15:01  小叽居biubiu  阅读(270)  评论(1编辑  收藏  举报