[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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章