樹上倍增求LCA詳解


LCA(least common ancestors)最近公共祖先

指的就是對於一棵有根樹,若結點z既是x的祖先,也是y的祖先,那么z就是結點x和y的最近公共祖先。

定義到此。

那么怎么求LCA

對於朴素思想,就是我要一步一步往上爬,一步一步走。先把結點x和y整到同一深度,然后再一次一個深度的往上查,直到祖先一樣才break(明顯是個while)

但是,一步一步實在是太慢了,所以不能腳踏實地地走

那么,考慮跳着走,

跳着走的條件就是要滿足一步步數盡可能多並且不跳過了。當然你跳過了在一步一步往下找也行啊QWQ

於是,在滿足這兩個條件的情況下,我們有了LCA算法:

一步跳2的n次方。

對於每一步跳躍,我們要預處理一個二維數組f(father)f[x][i],表示對於當前的x結點,往上跳2^i個祖先,特別的,像數學中的,f[x][0]就是x的一代祖先。於是我們要用一個dfs預處理來解決這個f數組。后面我們會提到;

處理完f數組,那就很簡單了。

輸入x和y,表示要求結點x和結點y的LCA,那么我們開始求LCA:

對於x和y在不同高度,我們先把他們拉到一個高度,同樣不能一步一步走,也要用到f數組。

這里我們要提前了解到一個定理:對於任意一個非零整數,我們都可以將他用2的次冪表示出來。也就是such as  : 11=2^3+2^1+2^0。這倒也不用證明,就像每個數都可以用二進制表示的原理。

接着講:在把x和y弄到同一高度時,我們要先做的是設x的深度dep要比y的深度dep要大,如果dep[y]>dep[x],那么把他們交換。原因是如果不交換,那么我們需要判斷兩種情況,用兩個if以及幾乎相同的代碼(亂七八糟還占內存),對於非常懶的我們,自然不會這么干。

然后用一個判斷    if(dep[f[x][i]]>=dep[y])x=f[x][i];(此時x深度大於y),在這里用外層for循環來枚舉i,但是i一定要從大到小!為什么?

安利一個大家都聽爛了故事:一個玻璃瓶,裝了幾塊石頭,滿到瓶口。滿了嗎?沒有。又裝了一些沙子,滿到瓶口。滿了嗎?沒有。最后又裝滿了水,滿到瓶口。終於滿了。

那么,類比於x和y的深度的距離,這個瓶子的容積也是同樣道理。從2的盡可能大的次冪去找,一旦能找到能接近他們的i,就更新dep[x],直到相等。類似於無限逼近,最后值相等的過程。如果i相等了,就說明他們的深度已經在同一層了。

與倍增到同一深度的過程相比,倍增找公共祖先也是類似的,但是有一點不同,就是你不知道什么時候找到他們的公共祖先,因此就沒有查找的上限,那么就讓他們盡可能接近。因此條件改成了這個:

if(f[x][i]!=f[y][i])
        {
            x=f[x][i];y=f[y][i];
        }

在執行完這個語句后,我們成功讓x和y變成了某一節點z的左右兒子!

因為左右兒子是最接近但是又不相等的。

那么我們隨便取其中一個找爸爸,就找到了LCA。

 

AC代碼:

有關鏈式存圖不懂的的點這里。

 

#include<cstdio>
#include<iostream>
using namespace std;
int n,m,s,num=0,head[1000001],dep[1000001],f[1000001][23];
int a1,a2;
struct edg{
    int next,to;
}edge[1000001];
void edge_add(int u,int v)//鏈式前向星存圖 
{
    num++;
    edge[num].next=head[u];edge[num].to=v;head[u]=num;
    edge[++num].next=head[v];edge[num].to=u;head[v]=num;
}
void dfs(int u,int father)//對應深搜預處理f數組 
{
    dep[u]=dep[father]+1;
    for(int i=1;(1<<i)<=dep[u];i++)
    {
        f[u][i]=f[f[u][i-1]][i-1];
    }
    for(int i=head[u];i;i=edge[i].next)
    {
        int v=edge[i].to;
        if(v==father)continue;//雙向圖需要判斷是不是父親節點 
        f[v][0]=u;
        dfs(v,u);
    }
}
int lca(int x,int y)
{
    if(dep[x]<dep[y])swap(x,y);
    for(int i=20;i>=0;i--)//從大到小枚舉使x和y到了同一層 
    {
        if(dep[f[x][i]]>=dep[y])x=f[x][i];
        if(x==y)return x;
    }
    for(int i=20;i>=0;i--)//從大到小枚舉 
    {
        if(f[x][i]!=f[y][i])//盡可能接近 
        {
            x=f[x][i];y=f[y][i];
        } 
    } 
    return f[x][0];//隨便找一個**輸出 
}
int main(){
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;i++)
    {
        scanf("%d",&a1);scanf("%d",&a2);
        edge_add(a1,a2);//鏈式存邊 
    }
    dfs(s,0);
    for(int i=1;i<=m;i++)
    {
        scanf("%d %d",&a1,&a2);
        printf("%d\n",lca(a1,a2));//求兩個節點的LCA 
    }
} 

 

完結撒花✿ヽ(°▽°)ノ✿有疑問或者有可以改進講解的地方可以指出。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM