淺談倍增法求LCA

在有根樹T中,兩個節點a、b的最近公共祖先(Lowest Common Ancestors,LCA)是某個點p,使得p同時是a、b的祖先,而且p離根最遠。

在樹上求最短路時,相較於其他的最短路算法,一些快速求LCA的算法有更高的效率。

這裏我們介紹以倍增法爲基礎的LCA算法。(又稱“跳錶法”);
具體過程是使用dfs建樹,維護具體信息,再用倍增法巧妙求得LCA,便於理解記憶

在dfs的過程中維護三個數組:
depth[i],表示i點在樹中的深度;
grand[x][i],表示x的第2^i個祖先的節點編號;
gdis[x][i],表示x到它2^i祖先的距離。

初始化:

const int maxn = 40010;
const int maxlog = 20;//maxlog = log2(maxn)
int grand[maxn][maxlog],gdis[maxn][maxlog];
int depth[maxn];
int n,m,q,s;

void Init(){
    //n爲節點個數 
    s = floor(log(n + 0.0) / log(2.0));//最多能跳的2^i祖先
    depth[0]=-1;//根結點的祖先不存在,用-1表示 
    dfs(1);//以1爲根節點建樹 
} 

這是dfs過程:
每次dfs先更新x結點的grand和gdis,然後搜索與x相連的結點

void dfs(int x){//dfs建樹 
    for(int  i = 1; i <= s; i++){
        grand[x][i] = grand[grand[x][i-1]][i-1];//x的2^i祖先是它2^i-1祖先的2^i-1祖先 
        gdis[x][i] = gdis[x][i-1] + gdis[grand[x][i-1]][i-1];//x到它2^i祖先的距離 = x到它2^i-1祖先的距離 + x的2^i-1祖先到它2^i-1祖先距離
        if(!grand[x][i]) break; //到子樹的根了 
    }
    for(int  i = 0; i < G[x].size(); i++){
        Edge & e = edges[G[x][i]]; 
        if(e.to != grand[x][0]){//與x相連的結點,不是它的父親,就是他的兒子
            depth[e.to] = depth[x] +1;//維護深度 
            grand[e.to][0] = x;//維護父子關係 
            dis[e.to][0] = e.dist;//維護距離 
            dfs(e.to);
        }
    }
}

這是求LCA過程:
將a,b兩個結點從它們原本的深度向上“跳”,直到它們到達它們的LCA,一邊跳一邊加上邊權
採取先讓深度大的點跳,再讓兩個點一起跳的方式

int LCA(int a, int b){
    if(depth[a] > depth[b]) swap(a, b);//保證a在b上面,便於計算
    int ans = 0;
    for(int i = s; i >= 0; i--) //類似於二進制拆分,從大到小嚐試
        if(depth[a] < depth[b] && depth[grand[b][i]] >= depth[a])//a在b下面且b向上跳後不會到a上面
            ans +=gdis[b][i], b =grand[b][i];//先把深度較大的b往上跳 
    for(int i = s; i >= 0; i--)
        if(grand[a][i] != grand[b][i])//a,b的2^i祖先不一樣 => 沒跳到同樣深度位置,接着跳
            ans += gdis[a][i], ans += gdis[b][i], a = grand[a][i], b = grand[b][i];//一起往上跳 
    if(a != b) ans += gdis[a][0], ans += gdis[b][0];//沒跳到一個地方,那麼再往上一個結點就是它們的LCA 
    return ans;
}

妙不可言的模板題鏈接:
[USACO FEB04]距離諮詢

注:存圖方式爲劉汝佳《入門經典》介紹的vector鄰接表,代碼如下:

struct Edge{ 
    int from,to,dist;
    Edge(int u,int v,int w):from(u),to(v),dist(w){}
};
const int maxn = 40010;//節點數
vector<Edge> edges;//邊的具體信息
vector<int> G[maxn];//邊的編號

void addEdge(int u, int v, int w){//加入一條邊(無向圖)
    edges.push_back(Edge(u,v,w));
    edges.push_back(Edge(v,u,w));
    int size = edges.size();
    G[u].push_back(size-2);
    G[v].push_back(size-1);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章