最近公共祖先(LCA)及其倍增算法实现

来源:互联网 发布:淘宝商品质量问题定义 编辑:程序博客网 时间:2024/06/12 01:13

最近公共祖先(LCA)

今天看看最近公共祖先(LCA),也就是所谓的最小公共祖先。
我们首先了解一下什么是LCA,我们通过几棵树来理解一下吧。
图一
如图所示,这棵树是以1为根节点的一棵树,我们举一个例子,3和5的LCA就是2,4和5的LCA就是1,3和2的LCA就是2本身。是不是有点明白?
接下来,我们不改变节点间的关系,只改变根节点。
图二
如图所示,我们把2作为根节点,那么这棵二叉树俨然就变成了多叉树,然后我们再举几个例子,比如说,3和5的LCA仍然是2,但4和5的LCA就变成了2,所以我们发现,根节点选取的改变,也会导致两点之间LCA的数值的变化。
理清概念之后我们就上一个模板题来看看,这个题的样例可以自己出,在这里不再给大家提供样例,话说可以去luogu过一下嘞。

题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。
输入格式:
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。
(数据保证可以构成树)。
接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。
输出格式:
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。 时空限制:1000ms,128M

学过最小生成树(MST)的人都应该知道,像这样的树,它的边数为N-1条。所以就算是题目中不说,我们也认为它是N-1条。
接下来我们就一步一步的解释:

在这里我们先提前引入一个数组,稍后讲倍增的时候会解释。fa[i][j]的含义就是,对于一个节点i,它的第2^j个父亲节点的编号,比如说上面第一个图中fa[5][0]=2,fa[5][1]=1。

做好图论题,比较基础且重要的就是建图,利用邻接表,从根节点出发,用DFS来建表。与其它题不同的一点就是这里每次son的建边都需要对它的fa[son][]数组进行更新,fa[son][i+1]=fa[fa[son][i]][i]。理解这一点很重要,这里就已经有倍增算法的思想了,可以自己任意画一棵树来试一试,是很奇妙的。

然后求LCA。

对于两个点X和Y,一个比较自然的思路就是,既然要找他们的LCA,就一个一个的顶上去就行呗,等到他们走过的路径有节点的时候就输出。这样做确实能得到正确的答案,但是会超时。

我们的思路是,首先保证deep[X]>=deep[Y],这保证了我们在操作的时候只操作X(当然只操作Y也是可以的)。然后让X向上搜,直至其深度等于Y的深度,这里通过倍增算法让其快速上搜,稍后我们解释倍增算法。等到二者深度相同的时候,就让他们同时上溯至同一节点,此节点即为X与Y节点的LCA。

先把自己会的搞上去以便于理解。
建边操作(话说我前面写的几个博客的建边操作太鬼畜了有木有),用3个数组来存边的属性,就是邻接表。

void add(int xx,int yy){    nxt[++temp]=hd[xx];    y[temp]=yy;    hd[xx]=temp;}

然后是建图,上面也已经说了,需要注意的就是同一个节点不能被经历两次,所以用一个bool数组来记忆。

void dfs(int x){    int p,son;    p=hd[x];    while(p)    {        son=y[p];        if(!b[son])        {            b[son]=1;            fa[son][0]=x;            deep[son]=deep[x]+1;            int ii=0,po=x;            while(fa[po][ii]!=0)            {                fa[son][ii+1]=fa[po][ii];                po=fa[po][ii];                ii++;            }            dfs(son);        }        p=nxt[p];    }}

接下来重点说倍增算法在LCA中的应用(详见注释)。

int lca(int xx,int yy){    int i,j;    if(deep[xx]<deep[yy])swap(xx,yy);    for(i=0; (1<<i)<=deep[xx]; i++);    i--;//找当前点到树根距离大小的2^j距离中,j的最大值。比如说:deep[xx]=3时,jmax=2,deep[xx]=9时,jmax=3。    for(j=i; j>=0; j--)        if(deep[xx]-(1<<j)>=deep[yy])            xx=fa[xx][j];    if(xx==yy)return xx;//当xx与yy同深度时,恰好就在yy这个位置上,那么就直接xx或者yy是LCA    for(j=i; j>=0; j--)    {        if(fa[xx][j]!=fa[yy][j])//这里的IF语句功能比较多,一方面可以筛掉超过当前点到树根距离大小的2^j距离,还可以在二者相同时不操作它,因为已经达成找到LCA的小目标了。        {            xx=fa[xx][j];            yy=fa[yy][j];        }    }    return fa[xx][0];}

这里LCA讲的可能比较糙,思路可以看看这个博客:
http://www.cnblogs.com/yyf0309/p/5972701.html
我是从这里学会的LCA的倍增实现的:
http://blog.csdn.net/wangwangbu/article/details/51453084
最后,有任何疑问欢迎评论区提出!
这里写图片描述

原创粉丝点击