1.什么是LCA
LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。——百度百科
这么说可能不直观,我们通过一张图来理解一下
(图片来源百度百科)
在这棵树中,点3和点6的最近公共祖先是2,因为3的祖先是{1,2},6的祖先是{4,2,1}。它们的公共祖先是{2,1},而其中深度最大的是2
2.怎么求LCA
1.朴素算法
了解了LCA的定义,不难得出一种暴力算法,即对于u,v两个点,我们让它们不断地一步一步往上跳,直到第一次相遇。
算法时间复杂度:O(N)
2.倍增算法
既然一步一步跳太慢,我们可以考虑设计一种倍增算法,让它一次跳20,21,···.2n次方步。
算法流程:
定义fa[i][j]为第i个点跳2j步后达到的点,deep[i]为第i个点的深度。
1.建树,计算深度,预处理fa[i][0]
这一部分可以用dfs实现
1 | void dfs(int t , int father) //t表示当前点编号,father表示它的父亲结点编号 |
2.预处理fa[i][j]
因为2j=2j−1+2j−1,所以我们可以得到转移方程
fa[i][j]=fa[fa[i][j−1]][j−1]
即先跳2j−1到达fa[i][j−1],再跳2j−1步到达fa[fa[i][j−1][j−1]]
Code
1 | void prework() |
3.求LCA
假设求x,y的LCA
为了方便讨论,假设deep[x]>deep[y],即x的深度大于y的深度
先让x跳到与y同一深度,代码如下
1 | for(int i = 20; i >= 0; i --) if(deep[fa[x][i]] >= deep[y]) x = fa[x][i];//只要x还比y深,就跳,否则不跳 |
如果这时候如果x已经等于y了,直接输出即可。
到达同一深度后,我们开始让x,y一起跳,注意这里我们要跳到的是它们LCA的下一层(因为我们跳到的是满足x和y不相等的最浅的一层,而它们的上层即为LCA)。
还是看最开始的例子,3,6,调整至同一高度后为3,4,,循环过后x和y的值并没有改变,因为我们跳到的是LCA的下一层。
代码如下
1 | for(int i = 20; i >= 0; i --) |
最后的答案就是f[x][0]了
完整代码
1 |
|
时间复杂度O(logn)
4.一些思考
为什么求LCA中j要从大到小枚举。
这个和天平称重时从大到小放砝码有点类似,举个例子,比如跳9步,如果按从小到大枚举是1+2+4+8,而9≠1+2+4+8,还需要回去重新找,而从大到小的话直接求出9=8+1
v1.5.2