倍增求LCA
LCA—最近公共祖先
LCA
所谓LCA
,是当给定一个有根树T时,对于任意两个结点u、v,找到一个离根最远的结点x,使得x同时是u和v的祖先,x 便是u、v的最近公共祖先(原问题涵盖一般性的有根树,为了简化,多使用二叉树来讨论)。例如下面:
结点3和结点4的最近公共祖先是结点2,即LCA(3, 4)=2 。在此,需要注意到当两个结点在同一棵子树上的情况,如结点3和结点2的最近公共祖先为2,即 LCA(3,2)=2。同理:LCA(5,6)=4,LCA(6,10)=1。
求LCA
的方法主要有:
- 倍增求
LCA
ST-RMQ
求LCA
Tarjan
求LCA
前面两者都是在线算法,最后一个是离线算法
在线和离线可以简单的理解为对于所有的操作是否需要读入完毕。
在线的要求是可以不用先知道所有的操作(类似询问、修改),边读入边执行,类似“走一步,做一步”的思想。
离线则与在线相反,要求必须知道所有的操作,类似“记录所有步,回头再做”的思想,一般用Query[]记录所有操作
转自:在线和离线算法 - 简书
倍增求LCA
倍增算法
所谓倍增,就是按2的倍数来增大,也就是跳 1,2,4,8,16,32 ……
不过在这我们不是按从小到大跳,而是从大向小跳,即按……32,16,8,4,2,1
来跳,如果大的跳不过去,再把它调小。这是因为从小开始跳,可能会出现“悔棋”的现象。拿 5为例,从小向大跳,$5≠1+2+4$,所以我们还要回溯一步,然后才能得出$5=1+4$;而从大向小跳,直接可以得出$5=4+1$。这也可以拿二进制为例,5(101),从高位向低位填很简单,如果填了这位之后比原数大了,那我就不填,这个过程是很好操作的。
求LCA
程序开始时选取任意节点为树根,进行dfs
,得到所有点的深度与pre[i][j]
。pre[i][j]
指节点 i 的第 $2^j$个祖先
如上图中,10 的第 1 个祖先是 9,第二个祖先是 8,第三个祖先是 7,第四个祖先是 1。所以 10 的第 2 的 0 次方个祖先是 9($2^0=1$),10 的第 2 的 1 次方个祖先是 8($2^1=2$),10 的第 2 的 2 次方个祖先是 1($2^2=4$)。很显然,10 没有 2 的 3 次方个祖先。所以pre[10][0]=9,pre[10][1]=8,pre[10][2]=1
而且通过倍增的思想,我们不难发现 i 的第 $2^j$ 个祖先就是 i 的第 $2^{j-1}$个祖先的第 $2^{j-1}$个祖先($j>=1$)。
比如当i=10,j=1
的时候,pre[10][1]=8
,pre[10][0]=9
,pre[9][0]=8
;或者由下面推出
$2^i = 2*2^{i-1} = 2^{i-1} + 2^{i-1}$
所以pre[i][j]=pre[pre[i][j-1]][j-1]
。
有了这个规律,我们就可以在 dfs
中预处理所有的 pre 了!
有了pre
,接着看看怎么求LCA
x、y分别是树上的两个点,找他们的最近的公共祖先
开头用一个判断,规定 x 的深度一定比 y 大,否则就交换一下。
- 首先让 x 往上跳,去找y的所在层
- 如果 x 和 y 重合了,就已经是答案了
- 否则 让 x 和 y 一起向上跳,直到找到答案
洛谷-P3379最近公共祖先
1 | /* |
参考:
节点的最近公共祖先 【倍增算法】 | Hang_c’s Blog
题解 P3379 liusu201601 的博客 - 洛谷博客