[Translate]CP-Algorithms:LowestCommonAncestor-BinaryLifting

一、原链接

翻译原链接: CP-Algorithms:LowestCommonAncestorBinaryLifting

二、翻译

最近公共祖先 - 二元提升

目录
  1. 算法介绍
  2. 算法应用
简介
假如G是一棵树。对于每一次输入为(u,v)的查询,我们希望找到结点u和v的最近公共祖先。例如,我们希望找到的目标结点为w,并且满足:
1). w在结点u到根节点的路径上,也在节点v到根节点的路径上。
2). 如果存在多个满足条件1的节点,那么就选择离根节点最远的节点。
也就是说,我们希望得到的节点w是u和v的最近公共祖先。特别的,假如节点u是v的祖先节点,那么u就是u和v节点的最近公共祖先。
算法介绍
  首先我们需要提前预计算每个与节点距离为2的幂次的祖先节点,例如计算在节点u上方,距离u为1(20)、2(21)、4(22)的祖先节点,并使用二维数组up存储。例如up[i][j]里存储的是与节点i相距2j的i节点的祖先节点,i的取值范围为[ 1, n],j的取值范围为[0, ceil(log(n))]。我们可以根据数组up[i][j]里的信息,在O(logN)的时间复杂度内得到任意节点的、距离节点任意高度的祖先节点。我们可以使用DFS遍历整个二叉树计算生成up数组。
  对于每个节点,我们也可以记录访问、离开该节点的时间来判断两个节点是否为'祖先-子孙'关系。例如,在DFS遍历中,如果先访问节点u,再访问节点v,并且先离开节点v,后离开节点u,那么说明u一定是v的祖先。
  假设现在我们受到一个查询请求(u,v),我们可以根据上一段中提到的方法判断两个节点是否存在'祖先-子孙'关系。如果u不是v的祖先节点,并且v也不是u的祖先节点,我们可以沿着二叉树往上搜索u的所有祖先节点,直到我们找到一个最高的节点x,并且满足x是u的祖先节点、不是v的祖先节点,并且up[x][0]是v的祖先。我们可以利用数组up[][]在O(log(n))的时间复杂度内找到x节点。
  接下来对算法进行详细介绍。假设L=ceil(log(N))。首先令i等于L,假如up[u][i]不是v的祖先节点,那么我们可以向上搜索,将u赋值为up[u][i],并且让i自减1。如果up[u][i]是v的祖先节点,那么我们只需要让i自减1,然后再判断up[u][i]是否是v的祖先节点,不断重复次过程,直到i<0,最后的u就是我们需要得到的节点x,此时u(x)依旧不是v的祖先节点,但是up[u][0]是v的祖先节点。
  此时,节点u和节点v的最小公共祖先(lowest common ancestor,LCA)就是up[x][0]。
  总结来讲,使用二元提升(binary lifting)算法计算LCA需要对i从ceil(log(N))迭代到0,每次迭代过程中判断up[u][i]节点是否是v的祖先节点(注意,判断up[u][i]中u不是一直不变的,比如在上上段中加粗部分提到,对u进行了赋值修改)。所以对于每次查询算法的时间复杂度为O(log(N))。
算法应用
int n, l;
vector<vector<int>> adj;
int timer;
vector<int> tin, tout;
vector<vector<int>> up;

void dfs(int v, int p)
{
    tin[v] = ++timer;
    up[v][0] = p;
    for (int i = 1; i <= l; ++i)
        up[v][i] = up[up[v][i-1]][i-1];
    for (int u : adj[v]) {
        if (u != p)
            dfs(u, v);
    }
    tout[v] = ++timer;
}
bool is_ancestor(int u, int v)
{
    return tin[u] <= tin[v] && tout[u] >= tout[v];
}

int lca(int u, int v)
{
    if (is_ancestor(u, v))
        return u;
    if (is_ancestor(v, u))
        return v;
    for (int i = l; i >= 0; --i) {
        if (!is_ancestor(up[u][i], v))
            u = up[u][i];
    }
    return up[u][0];
}
void preprocess(int root) {
    tin.resize(n);
    tout.resize(n);
    timer = 0;
    l = ceil(log2(n));
    up.assign(n, vector<int>(l + 1));
    dfs(root, root);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值