淺談倍增查找LCA

前言

這篇文章,是一篇總結倍增查找LCA的一篇文章,同時也是實現在淺談RMQ算法文章中承諾.若有疑問或建議,請於下方留言,謝謝!

LCA是什麼?

LCA(Least Common Ancestors),即最近公共祖先,是指在有根樹中,找出某兩個結點u和v最近的公共祖先

如何實現LCA?

  1. 暴力直接找
    1. 首先將u,v中深度較深的那個點蹦到和較淺的點同樣的深度
    2. 然後兩個點一起向上蹦直到蹦到同一個點,這個點就是它們的LCA
    3. 時間複雜度在極端條件下可以到O(N)
  2. 運用DFS序
    1. DFS序就是用DFS方法遍歷整棵樹得到的序列。
    2. 兩個點的LCA一定是兩個點在DFS序中出現的位置之間深度最小的那個點
    3. 尋找最小值可以用使用我們前篇文章講的RMQ
  3. 運用倍增思想
    1. 我們令father[i][j]表示編號爲i的點,往上蹦2^j次的父親是誰,其實這個預處理和RMQ很相似,先從大到小枚舉j,然後令father[i][j]=father[father[i][j-1]][j-1],這樣預處理的時間複雜度爲O(NlogN)
    2. 接下來是查詢,可以分兩步走
      1. 將u和v移動到同樣的深度
      2. u和v同時向上移動,直到重合。第一個重合的點即爲LCA
    3. 再是移動到同樣的深度
      1. 令u爲深度較大的點。我們從log2n  , 到0枚舉,令枚舉的數字爲j。如果從u向上跳2j 步小於了v的深度,不動;否則向上跳2j 步。這樣一定能移動到和v一樣的深度。
      2. 假設一共要跳k步,上面的算法相當於枚舉k的每個2進製爲是0還是1
    4. 從同樣的深度移動到同一個點
      1. 和上一步類似。從log2n  到0枚舉,令枚舉的數字爲j。如果兩個向上跳2j 步將要重合,不動,否則向上跳2j 步。
      2. 通過這種辦法u和v一定能夠到達這樣一種狀態——它們當前不重合,如果再向上一步就重合。所以再上一步就得到了LCA
    5. 由於本質上是枚舉每一個二進制位,所以單次查詢的複雜度爲log2n 

我們可以注意到,在整個倍增查找LCA的過程中,從u到v的整條路徑都被掃描了一遍。如果我們在倍增數組F[i][j]中再記錄一些別的信息,就可以實現樹路徑信息的維護和查詢

LCA模板題

這裏給一個LCA用倍增思想的模板,題目來源codevs4605 (傳送門

題目描述 Description

顧名思義.給一棵有根樹,以及一些詢問,每次詢問樹上的2 個節點A、B,求它們的最近公共祖先.

輸入描述 Input Description

第一行一個整數N.

接下來N 個數,第i 個數Fi 表示i 的父親是Fi. 若Fi = 0,則i 爲樹根.

接下來一個整數M.

接下來M 行,每行2 個整數A、B,詢問節點(A xor LastAns)、(Bxor LastAns)的最近公共祖先. 其中LastAns 爲上一個詢問的答案,一開始LastAns = 0.

輸出描述 Output Description

對每一個詢問輸出相應的答案.

樣例輸入 Sample Input

10
0 1 2 3 2 4 2 5 4 9
10
3 9
2 7
7 8
1 1
0 6
6 11
6 3
10 7
2 15
7 7

樣例輸出 Sample Output

3
1
4
5
2
4
2
5
2
5

數據範圍及提示 Data Size & Hint

30% n,m≤1000
100% n,m≤100,000
代碼
不解釋了吧,上代碼

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<queue>
#include<vector>
using namespace std;
int n,m;
vector <int> a[100001];
int f[100001][18];
int father[100001];
int root;
int height[100001];
int lca(int x,int y)
{
        if(height[x]<height[y])
        {
           int tt=x;
           x=y;
           y=tt;                       
        } 
        int t=0;
        while((1<<t) <= height[x])t++;
        t--;
        int i;
        for(i=t;i>=0;i--)
        {
           if(height[x]-(1<<i)>=height[y])
             {
                 x=f[x][i];                             
             }                 
        }
        if(x==y)return x;
        for(i=t;i>=0;i--)
        {
           if(f[x][i]!=f[y][i])
           {
              x=f[x][i];
              y=f[y][i];                    
           }                 
        }
      return f[x][0];  
}
void dfs(int x,int deep)
{
     int i,j;
     height[x]=deep;
     for(i=1;i<=17;i++)
     {
         f[x][i]=f[f[x][i-1]][i-1];                  
     }   
     for(i=0;i<a[x].size();i++)
     {
         dfs(a[x][i],deep+1);                          
     }
}
int main()
{
   scanf("%d",&n);
   int i,j;
   for(i=1;i<=n;i++)
   {
     scanf("%d",&father[i]);  
     f[i][0]=father[i];
     if(f[i][0]==0)
     {
        root=i;              
     }
     a[father[i]].push_back(i);
   }
   dfs(root,0);
   scanf("%d",&m);
   int ans=0;
   int x,y;
   for(i=1;i<=m;i++)
   {
       scanf("%d%d",&x,&y);                
       x=x^ans;y=y^ans;
       ans=lca(x,y);
       printf("%d\n",ans);
   }
   return 0;    
}
發佈了39 篇原創文章 · 獲贊 7 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章