LCA最近公共祖先
https://www.cnblogs.com/hulean/p/11144059.html
题目:最近公共祖先LCA
\(LCA\),最近公共祖先是指在有根树中,找出某两个结点\(u\) 和 \(v\) 最近的公共祖先。
怎么求?
当要求两个结点的\(LCA\),我们可以先让这两个结点跳到同一高度,然后同时往上跳,直到跳到它们公共的\(LCA\)。但是我们会浪费很多不必要的步骤,一步一步地跳太浪费时间了,有什么更简便的方法吗。这里我们需要用到一种方法——倍增的思想。所谓倍增的思想就是。
比如当前\(depth(x) = 9\),即我们要跳 \(9\) 步才能跳到根节点\(log_29 = 3\),\(9 = (1001)_2 = 8 + 1\) ,可以先跳\(8\)步,再跳\(1\)步。
这里有一个性质:由于任意步数都可以由若干个二进制数位表示(即\(2\)的倍数累加),所以我们可以先初始化当前结点的 \(2^i(i = 0,1,2...)\) 级祖先结点的值。
- 常数优化:我们先初始化\(lg(i) = log_2(i) + 1\)的值,真实的值应该为\(lg(i) - 1\)
for(int i = 1; i <= n;i++)
lg[i] = lg[i-1] + (1 << log[i-1] == i);
- 初始化结点深度和它们的 \(2^i\) 级祖先
转移: \(now\)的 \(2^i\) 级祖先等于 \(now\) 的\(2^{i-1}\)祖先的 \(2^{i-1}\)祖先 \((2 ^i = 2 ^{i-1} + 2 ^{i-1})\)
void dfs(int now, int fath) {
fa[now][0] = fath, depth[now] = depth[fath] + 1;
for(int i = 1;i <= lg[depth[now]] - 1; i++) {
f[now][i] = fa[fa[now][i-1]][i-1];
}
for(int i = h[now]; i != -1;i = ne[i]) {
int j = e[i];
if(j != fath) dfs(j, now);
}
}
- 倍增\(LCA\)
先让两点跳到同一高度:假设\(depth[x] - depth[y] = 10\),\(log_2[10] = 3\),先\(x = fa[x][3]\)跳到\(x\)的第\(8\)级祖先。
此时\(depth[x] - depth[y] = 2\), 再跳\(log_22 = 1,即2^1\) 级祖先到达\(y\)结点。可以看出跳的时候都是以\(2\)的倍数在跳,直到这些\(2\)的倍数加起来的和为深度差。
int LCA(int x, int y) {
if(depth[x] < depth[y]) swap(x, y); // 假设x的深度更大
while(depth[x] > depth[y]) {
x = fa[x][lg[depth[x] - lg[depth[y]]] - 1];
}
if(x == y) return x; // y是x的祖先,提前返回LCA
for(int k = lg[depth[x]] - 1; k >= 0; )
if(fa[x][k] != fa[y][k])
x = fa[x][k], y = fa[y][k];
return fa[x][0];
}
这里讨论最后一步的情况:
如这张图,求\(3和4\)的 \(LCA\),\(log_2(depth(3)) = 1\),\(fa[3][1] = 1, fa[4][1]=1\),这个时候它们已经跳到根节点来了,但是它们是相等的,我们要跳过这种情况,因为无法确定它们是\(LCA\)还是\(LCA\)的祖先。我们需要让它们跳到\(LCA\)的下一层高度。所以我们尽量选大的步数跳,从\(log_2(depth(x)) -->0\) 遍历,当跳完之后的\(x\)和\(y\)不相同时说明它们还在\(LCA\)下面,这时候让它们跳上来,然后再按照之前跳的步数的一半继续跳。可见\(LCA\)算法跳的时候并不是固定的,也会有一些无效的 “跳”。那么最后跳完\(x\)和\(y\)依然不相同,此时它们的上一个父节点就是\(LCA\),即\(fa[x][0]所求\)。
如本题中的\(17\)和\(18\)。18先跳到16时和17的高度一致。然后再一起跳
此时\(depth(17)=6\), \(log_26 = 2\),直接先跳 \(2^2\) 步到 $ 3$。16也跳到\(3\),但是由于\(fa[17][2] == fa[16][2]\),不作数,保留原地不动。
然后\(k=1\),一次跳两步(上次跳多了,减一半),17 --> 10, 16 --> 8。
最后\(k = 1\),一次跳一步,17 --> 10 --> 7, 16 --> 8 --> 5
\(k = 0\)结束过程,最终答案\(fa[7][0] = 3\)