1. 程式人生 > >[模板]LCA的倍增求法解析

[模板]LCA的倍增求法解析

clu log 指定 orz name 一個點 swa 輸入輸出格式 請求

題目描述

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。

輸入輸出格式

輸入格式:

第一行包含三個正整數N、M、S,分別表示樹的結點個數、詢問的個數和樹根結點的序號。

接下來N-1行每行包含兩個正整數x、y,表示x結點和y結點之間有一條直接連接的邊(數據保證可以構成樹)。

接下來M行每行包含兩個正整數a、b,表示詢問a結點和b結點的最近公共祖先。

輸出格式:

輸出包含M行,每行包含一個正整數,依次為每一個詢問的結果。

輸入輸出樣例

輸入樣例#1:
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
輸出樣例#1:
4
4
1
4
4

說明

時空限制:1000ms,128M

數據規模:

對於30%的數據:N<=10,M<=10

對於70%的數據:N<=10000,M<=10000

對於100%的數據:N<=500000,M<=500000

樣例說明:

該樹結構如下:

技術分享

第一次詢問:2、4的最近公共祖先,故為4。

第二次詢問:3、2的最近公共祖先,故為4。

第三次詢問:3、5的最近公共祖先,故為1。

第四次詢問:1、2的最近公共祖先,故為4。

第五次詢問:4、5的最近公共祖先,故為4。

故輸出依次為4、4、1、4、4。

Solution:

題目來源:Luogu P3379

我們來講講LCA的倍增求法,來自於LS學長神犇的教授,加上我自身的理解,可能對各位新人的幫助會更有理解作用,所以我決定分享一下。(蒟蒻一本正經)

LCA——最近公共祖先。最樸素的算法無疑是從兩個點一個個往上走,出現的第一個兩個點都走過的點即為兩點的LCA。這個無需多加解釋。

然而這樣時間復雜度非常高。尤其是當樹退化成一條鏈的時候,每次查詢的復雜度將會飆到O(N)。

為此,倍增的作用就是將兩點上升所需的復雜度減低

流程概括為:將深度不同的兩點跳到同一層(深一點的跳到淺一點的一層),然後再將兩點向上跳到LCA的下面一層,最後再向上跳一層。(後面一部分下面會解釋,為什麽不能直接跳到LCA。)

快速跳的方法,每次向上跳的層數都為2^i層。(i為非負整數),相當於把層數差轉成2進制數,一位一位往上跳,使層數差快速減小。這樣,每次查詢復雜度最高就只會為log2(N)。

實現方法:

我們首先需要兩個數組。f[i][j]代表從i點向上跳2^j層後所到達的點,dep[i]代表這棵樹中點i的深度

dep數組可以在從根深度遍歷整棵樹的時候求得。

f數組利用了遞推的思想。遞推式為:f[i][j]=f[f[i][j-1]][j-1]。初始化f[i][0]也可以在遍歷整棵樹的時候求得。

之後,我們處理LCA時,按照上面的流程。我們先將較深的點一次次往上逼近較淺的點,直至他們層數相同。(相當於把層數差的二進制一位一位減去,直至變成0)

然後,兩點再同時向上逼近。i從最高位開始枚舉,假設兩點分別為x,y,那麽能向上跳的判斷式為:if f[x][i]!=f[y][i] then x=f[x][i],y=f[y][i]。翻譯成人話就是如果兩點向上跳了2^i層以後不到同一個點就接著往上跳。為什麽這樣?因為如果往上跳了2^i層,即使到了同一個點,它不一定是兩點的LCA。

這樣做,最終就會到達LCA的下面一層。隨後,我們再將兩點向上跳一層。LCA求得。

為什麽最終會到達LCA的下面一層?

我們假設從a,b點開始,往上跳2^j層,跳到同一點。不跳。往上跳2^(j-1)層,不跳到同一點,往上跳,分別到了A‘,B‘。顯然,這種情況是一定會存在的。

那麽,從A‘,B’再往上跳到原來那個決定不跳的點,顯然要跳2^(j-1)層。那麽,那個點有可能是LCA,也有可能不是,對吧?所以,從A‘,B‘往上跳到LCA所需的層數,是≤2^(j-1)的。

技術分享

所以,從A‘,B‘跳到LCA的下面一層X的所需層數,會是<2^(j-1)的。換句話來說,A‘,B‘到X的層數變成了一個j-2位的二進制數(可能會有前導零,也就是還可能會跳到點數相同的地方)。而此時,剛好枚舉到j-2位。那麽,前導零不減,再這麽減下去,你發現,這個層數差最終會變成0,而你最終也會到達第X層。

請最好自己手畫一棵樹模擬一下整個算法的過程。

蒟蒻解釋難免有錯誤,如果出現錯誤,請回復我,我將感激不盡orz

代碼如下:

#include<bits/stdc++.h>
using namespace std;
const int N=500010,OP=log2(500000)+1;
int dpt[N][26];
int dep[N];

int gi(){
    int x=0;
    char ch=getchar();
    while(ch<0||ch>9)ch=getchar();
    while(ch>=0&&ch<=9)x=x*10+ch-0,ch=getchar();
    return x;
}

int h[N],to[N*2],nexp[N*2],p=1;
inline void ins(int a,int b){
    nexp[p]=h[a],h[a]=p,to[p]=b,p++;
}

void dfs(int p,int x){
    dep[x]=p;
    for(int u=h[x];u;u=nexp[u]){
        if(!dep[to[u]]){
            dpt[to[u]][0]=x;
            dfs(p+1,to[u]);
        }
    }
}

int LCA(int a,int b){
    if(dep[a]<dep[b])swap(a,b);
    for(int j=OP;j>=0;j--)
        if(dep[a]-(1<<j)>=dep[b])a=dpt[a][j];
    if(a!=b){
        for(int j=OP;j>=0;j--)
            if(dpt[a][j]!=dpt[b][j])a=dpt[a][j],b=dpt[b][j];
        a=dpt[a][0];
    }
    return a;
}

int main(){
    int n,q,r;
    cin>>n>>q>>r;
    int a,b;
    for(int i=0;i<n-1;i++){
        a=gi(),b=gi();
        ins(a,b),ins(b,a);
    }
    dfs(1,r);
    for(int j=1;j<=OP;j++)
        for(int i=1;i<=n;i++)
            dpt[i][j]=dpt[dpt[i][j-1]][j-1];
    for(int i=0;i<q;i++){
        a=gi(),b=gi();
        printf("%d\n",LCA(a,b));
    }
    return 0;
}

[模板]LCA的倍增求法解析