完整部分点这里
有 N 种办法可以解决LCA问题,这里我们只讲一种,用树上倍增的方法来实现 LCA。
在没有学习倍增写LCA之前,你是怎么样求LCA的呢?至少,我是老老实实地让这两个点一步一步往上移并找出它们的路径第一次交汇的地方。这种方法固然可行、好想,但它的效率实在不高。但是,我们完全可以通过提高“这两个点一步一步往上移”来提高效率。
所以,我们采用倍增的思路来预处理,分别记录这点的祖先,记录为anc[i][j]。即为第i个点往上2^j个祖先。比如说,当j=0时,2^j=1,anc[i][j]是第i个点的上一个节点,即它的父亲节点。
那么该如何预处理出anc数组呢?
int anc[1005][25], fa[1005], deep[1005];
vector<int> tree[1005];
void dfs(int x)
{
anc[x][0]=fa[x];
for (int i = 1;i <= 20;i++)
anc[x][i] = anc[anc[x][i - 1]][i - 1];
for (int i = 0;i < tree[x].size();i++)
if (tree[x][i] != fa[x])
{
int y = tree[x][i];
fa[y] = x;
deep[y] = deep[x] + 1;
dfs(y);
}
}
接着,只要调用dfs(根节点)就可以初始化anc数组。
下面,我们来考虑如何处理LCA查询。即每次给你两点X和Y,求出它们的LCA(X,Y)。在有了ANC数组之后,求出最近公共祖先就会变得很简单。
首先,让X,Y在同一深度上。在大多数情况下,查询给你的两个点X和Y它们的深度是不同的。但是,如果两点的深度相同,我们就可以实现两个点同时倍增比较何时祖先相同。所以,第一步是使X,Y中深度较深的点往上移动直到与另一个点深度相同。当然,点的移动也可以用倍增完成。
然后,当两点深度相同后,同时向上倍增两个点,当它们祖先刚好相同时,这个祖先就是它们的LCA。
int LCA(int x, int y)
{
if (deep[x] < deep[y]) swap(x, y);
for (int i = 20;i >= 0;i--)
if (deep[y] <= deep[anc[x][i]])
x = anc[x][i];
if (x == y) return x;
for (int i = 20;i >= 0;i--)
if (anc[x][i] != anc[y][i])
{
x=anc[x][i];
y=anc[y][i];
}
return anc[x][0];
}
我们首先让x成为深度较深的点,第一个for让x和y的深度相同。如果y是x的祖先,直接可以退出。然后同时开始倍增直至到达同个节点。输出。