【算法学习笔记】04 最近公共祖先LCA
原理
顾名思义,就是求两点的最近公共祖先(自己也是自己的祖先)。
也就是两点在走到根节点的路径上最先遇到的共同的点。
向上标记法
比较贴定义的原始方法。
一点先向 /(root/) 走,走过的点标记一下;然后另一点也往 /(root/) 走,走到的第一个被标记的点为二者的 /(LCA/)。时间复杂度为 /(O(n)/)
倍增法
1. 首先需要记录两个变量
1. /(f(i, j)/)
表示从 /(i/) 开始,向上走 /(2^j/) 步能到达的节点,/(0/leq j/leq logn/)
那么怎么更新?
- /(j=0/), /(f(i,j)/) 为 /(i/) 的父节点
- /(j>0/), /(f(i,j)=f(f_(i,j-1),j-1)/),相当于跳了两个 /(2^{j-1}/)
这一过程可用 /(bfs/dfs/) 求
2. /(depth(i)/)
表示 /(i/) 的深度
哨兵
/(depth(0)=0/)
如果从 /(i/) 开始跳 /(2^j/) 步会跳过根节点,那么 /(f(i,j)=0/)。
2. 将两点跳到同一层
具体操作如下:
相当于用我们之前处理好的二进制数来拼凑 /(dx = |depth(x)-depth(y)|/),即从后往前找到第一个满足 /(2^k /leq dx/) 的 /(k/), 然后令 /(dx -= 2^k/), 重复这个找的过程,直至 /(dx=0/)。
找的过程不用算具体值,只需判断是否满足 /(depth(f(x,k))/geq depth(y)/)即可。
3. 让两个点一直往上跳,直到跳到 /(LCA/) 的下一层(儿子那层)
为什么要到下一层?
因为到本层(/(LCA/))时无法判断是否为 /(LCA/),为避免歧义才到下一层。
跳的过程也是二进制拼凑,从大到小枚举 /(k/), 直至 /(f(a,k)=f(b,k)/), 此时到了点/(x, y/),在往上一层,也就是他们的父亲就是 /(LCA/),即 /(LCA=f(x,0)=f(y,0)/)。
板子
先贴一个y总的板子来吓吓我的vsc,明天来自己写一遍
void bfs(int root)
{
memset(depth, 0x3f, sizeof depth);
depth[0] = 0, depth[root] = 1;
int hh = 0, tt = 0;
q[0] = root;
while (hh <= tt)
{
int t = q[hh ++ ];
for (int i = h[t]; ~i; i = ne[i])
{
int j = e[i];
if (depth[j] > depth[t] + 1)
{
depth[j] = depth[t] + 1;
q[ ++ tt] = j;
fa[j][0] = t;
for (int k = 1; k <= 15; k ++ )
fa[j][k] = fa[fa[j][k - 1]][k - 1];
}
}
}
}
int lca(int a, int b)
{
if (depth[a] < depth[b]) swap(a, b);
for (int k = 15; k >= 0; k -- )
if (depth[fa[a][k]] >= depth[b])
a = fa[a][k];
if (a == b) return a;
for (int k = 15; k >= 0; k -- )
if (fa[a][k] != fa[b][k])
{
a = fa[a][k];
b = fa[b][k];
}
return fa[a][0];
}
离线的做法也没学
还没写题呢,明天一定(
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/280625.html