您的位置:首页 > 其它

[知识点]最近公共祖先

2015-11-01 18:36 316 查看
1、前言

  最近公共祖先(LCA),作为树上问题,应用非常广泛,而求解的方式也非常多,复杂度各有不同,这里对几种常用的方法汇一下总。

2、基本概念和暴力算法

  最近公共祖先,顾名思义,指的是两个点的公有祖先中,最近的那个点。它显然不会作为一个单独的知识点拿出来考,但是在很多题目中,出现的频率很高,不同场合用不同的方法。先考虑最简单的做法。找祖先,显然是从所查询的两个点从下往上爬,直到两个点出现第一次重叠时,就是最近公共祖先了。首先我们预处理出所有点的深度及父亲节点。由于两个点深度可能不同,首先我们要保证深度大的点先往上爬到与另一个点深度相同的地方,然后两个点同时向上爬,直到出现重叠。代码如下:

int LCA(int x, int y)
{
  if (dep[x] < dep[y]) swap(x, y);
  for (; dep[x] != dep[y]; x = fa[x]);
  for (; x != y; x = fa[x], y = fa[y]);
  return x;
}


3、倍增LCA

  倍增这个概念听名字很好理解,知道倍增思想的,直接运用到求LCA并不是不好理解。首先我们需要预处理出所有节点的各种祖宗关系,,p[i][j]记录节点i的第2^j个祖先,j = 0时,就是他的父亲节点。同上述暴力算法,我们从询问的两个点开始,往他们的这些倍数祖先向上爬,若第2^i个祖先不同,则从第2^(i - 1)个祖先开始,从头开始倍增。时间复杂度为 log 级别。代码如下:

void init()
{
for (int j = 1; (1 << j) <= n; j++)
for (int i = 1; i <= n; i++)
if (p[i][j - 1] != -1) p[i][j] = p[p[i][j - 1]][j - 1];
}

int query(int x, int y)
{
int o;
if (dep[x] < dep[y]) swap(x, y);
for (o = 0; (1 << o) <= dep[x]; o++);
o--;
for (int i = o; i >= 0; i--)
if (dep[x] - (1 << i) >= dep[y]) x = p[x][i];
if (x == y) return x;
for (int i = o; i >= 0; i--)
if (p[x][i] != -1 && p[x][i] != p[y][i]) x = p[x][i], y = p[y][i];
return p[x][0];
}


  P.S. 之前我一直很傻逼的不清楚,如果最近公共祖先出现在了第2^(i - 1)个祖先到底2^i个祖先之间怎么办啊。

4、树链剖分LCA

  树链剖分详细概念见(/article/6239479.html)。为什么树链剖分可以跑LCA?原文提得很清楚,在把重链划分出来之后,可以确定的是重链要么单独一条存在,要么必定相连,故可以用线段树来维护。

  树链剖分的优势和倍增是一样的,都可以跳过一些不必要的部分,若当前点的重链顶端不是父亲节点,可以利用线段树直接从当前点跳到顶端。这样可以达到减小复杂度的目的。

  这里写出树链剖分DFS预处理和LCA核心代码的最新版本。代码如下:

void DFS(int o, int d)
{
dep[o] = d, son[o] = 1;
for (int x = h[o]; x; x = edge[x].next)
{
int v = edge[x].v;
if (v == fa[o]) continue;
DFS(v, d + 1), son[o] += son[v], hv[o] = !hv[o] || son[hv[o]] < son[v] ? v : hv[o];
}
}

void DFS2(int o, int t)
{
top[o] = t;
if (!hv[o]) return;
DFS2(hv[o], t);
for (int x = h[o]; x; x = edge[x].next)
{
int v = edge[x].v;
if (v != hv[o] && v != fa[o]) DFS2(v, v);
}
}

int query(int x, int y)
{
int f1 = top[x], f2 = top[y];
while (f1 != f2)
{
if (dep[f1] < dep[f2]) swap(f1, f2), swap(x, y);
x = fa[f1], f1 = top[x];
}
return dep[x] < dep[y] ? x : y;
}


(注释:dep为节点深度,son为儿子节点个数,hv为重儿子节点,top为重链顶端,fa为父亲节点)

5、Tarjan LCA

(暂略)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: