倍增法求LCA

下圖是這篇文章的思路導航,本人將按照這個模式學習該算法。此文爲我的學習筆記,如有差錯感謝大家斧正。

一,最初的目的

     顯然就是求LCA了,LCA是啥? 中文解釋爲樹上兩點的最近公共祖先。有一個小bug,當兩個點在同一條支上的時候,LCA是可以爲其中一個點的。也就是說,LCA是可以包括着兩個點本身的。並不是嚴格意義上的祖先。

   這裏就加入一道板子題了:(題目鏈接P3379 【模板】最近公共祖先(LCA))

    題目答意:給你一棵樹,有 m 次查詢,每次問你 a, b,樹上兩點的最短距離。

    萬物始於暴力,LCA的樸素算法顯然也很簡單,就是先讓深度較深的向上爬直到兩點在同一深度上,如果這時兩點相遇(一點在以另一點爲根的子樹上)則LCA爲當前位置。如果還沒有相遇,就讓兩點同時向上移動,直到兩點相遇(兩點都在以LCA爲根的子樹上)。這裏的移動顯然是最耗費時間,如果有n個點m次查詢就爲O(nlog n)當樹退化時接近O(n^{2})

二,算法結構

    首先這是一個樹,不知道的可以出門找度娘。

    倍增顯然就是需要加速樸素算法中的上升過程了,這裏運用了一個類似DP的東西。

    我們要初始化一個 anc\left [ i \right ]\left [ j\right ] 數組,這個數組表示, i 這個節點由下往上第 2^{j} 位祖先是誰。顯然就有初狀態 anc\left [ i \right ]\left [ 0 \right ] 爲 i 的父節點,還有狀態轉移方程  anc\left [ i\right ]\left [ j \right ] = anc\left [ anc\left [ i \right ]\left [ j-1 \right ] \right ]\left [ j-1 \right ] 。這個轉移方程的中文表示法就是:我的第 2^{j-1} 位祖先的第 2^{j-1} 位祖先是我的第 2^{j} 位祖先。

    然後的過程就和樸素算法一樣了,爬高高。

    第一情況種:

              

     a 上升到與 b 同高相遇,LCA此時爲 b 。

     第二種情況:

              

    a b 同高後依然不相遇 , 就讓 a b 同時向上  直到 a b 同父。

    然後是代碼部分:

struct node_e{
    int v,en;
}e[maxn<<1];
int he[maxn],cnt;//前項星三件套

int vis[maxn],dep[maxn];//標記,深度
int anc[maxn][25];//祖先
int n,m,s;

void ade(int a,int b){
    e[++cnt].v=b;e[cnt].en=he[a];he[a]=cnt;
}
void dfs(int s){//初始化
    vis[s] = 1;
    for(int i=1;i<21;++i){
        if(dep[s]<(1<<i)) break;
        anc[s][i]=anc[anc[s][i-1]][i-1];//倍增求祖先
    }
    for(int i=he[s];i;i=e[i].en){
        int v = e[i].v;
        if(vis[v]) continue;
        dep[v]=dep[s]+1;
        anc[v][0]=s;
        dfs(v);
    }
}
int Lca(int a,int b){
    if(dep[a]<dep[b]){int tt=a;a=b;b=tt;}//確保a在下面
    int t = dep[a]-dep[b];
    for(int i=0;i<21;i++){
        if(t&(1<<i)) a=anc[a][i];//a向上爬到和b同樣的高度

    }
    if(a==b) return a;//a,b在一棵子樹上
    for(int i=20;i>=0;i--){//不在
        if(anc[a][i]!=anc[b][i]){
            a=anc[a][i];
            b=anc[b][i];
        }
    }
    return anc[a][0];
}

三,理解深入(未完待續)

四,刷題

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章