1. 程式人生 > >最近公共祖先(LCA) 【倍增演算法】

最近公共祖先(LCA) 【倍增演算法】

題目連結洛谷-P3379

## **題目描述**:

如題,給定一棵有根多叉樹,請求出指定兩個點直接最近的公共祖先。
在這裡插入圖片描述

思路

這是一個裸的LCA問題,即求書上兩個節點的最近公共祖先。
我們可以用樹上倍增來做;

當然,在做之前我們假設不知道該演算法。那麼我們如何來做這種型別的題目呢?
顯然,我們可以用暴力來做,找到兩點的最近公共祖先,我們可以用前向星存雙向邊,然後依次儲存每個點到的根的路徑。然後找到最先同時出現在兩條路徑的公共點即可;
顯然,這樣是可做的。但是這樣做,考慮到:
1.當書的深度非常大
2.當最壞情況樹退化為一條鏈式
3.當詢問次數非常多的時候。

以上,無論那種情況,起步都是O(N^2)的複雜度,顯然,這樣的做法並不夠優秀,所以我們引入倍增的做法,它可以NlogN的時間複雜度完成預處理,每次詢問LogN的時間給出回答;

倍增演算法:所謂倍增,就是按2的倍數來增大,也就是跳 1,2,4,8,16,32 …… 不過在這我們不是按從小到大跳,而是從大向小跳,即按……32,16,8,4,2,1來跳,如果大的跳不過去,再把它調小。這是因為從小開始跳,可能會出現“悔棋”的現象。拿 5為例,從小向大跳,5≠1+2+4,所以我們還要回溯一步,然後才能得出5=1+4;而從大向小跳,直接可以得出5=4+1。這也可以拿二進位制為例,5(101),從高位向低位填很簡單,如果填了這位之後比原數大了,那我就不填,這個過程是很好操作的。

lca的倍增做法就是 用一個fa[i][j]陣列 來儲存 i 節點的 2^j 倍父親是哪個節點; 這樣做每次都是2倍區間查詢,所以可以在logN的時間給出答案;

我們先跑一遍dfs, 初始化 深度 d陣列 , 初始化 fa[i][0] 。 跑一遍dfs後,d陣列儲存的就是每個節點的深度(預設根節點深度為1). fa陣列儲存的就是 fa[i][0] 就是 每個節點儲存的都是他父親節點的編號;

跑完dfs,我們需要對全域性fa進行賦值; 這裡用到的轉移方程是
fa[i][j] = fa[fa[i][j-1]][j-1] 。
比如(畫的比較醜)
在這裡插入圖片描述
2節點的 fa[2][0] 為 1 。 就是 2節點 的 (2^0 = 1)的父親為 1
4節點的fa[4][0]為 2。 就是 4號節點的父親為 2

要求 fa4 我們可以表示為 fa[fa[4][0]][0], 就是4號父親的父親, 這樣遞推上去,就可以的出每個fa;

完成這步之後,我們剩下就是求Lca了;

先把x節點和y節點挪到同一層, 挪到同一層後, 若 x == y 代表一開始 x 就是y的祖先或者y是x的祖先;

若 x != y, 則 我們找到最深的滿足 fa[x][i] != fa[y][i] ;
然後返回x的父親節點即可。
至此,我們的演算法就結束了;

lca倍增學習:很明白的lca教程

易錯點

1.建樹用前向星建, 注意邊數為n-1條,雙向邊,則應該開闢MAX_N * 2空間
2.此題輸入輸出量很大, 用scanf和printf輸入輸出;
3.這題好像卡常數的樣子? 初始化head和深度d的時候用迴圈初始化,memset會超時!

AC程式碼

#include <bits/stdc++.h>
using namespace std;

#define MAX_N 500050

int head[MAX_N], cnt = 0, d[MAX_N], fa[MAX_N][30];

struct Node{
    int to, next;
}edge[MAX_N * 2];

void add(int x, int y)
{
    edge[cnt].to = y;
    edge[cnt].next = head[x];
    head[x] = cnt++;
}

void dfs(int v)
{
    for(int i = head[v]; i != -1; i = edge[i].next)
    {
        int k = edge[i].to;
        if(d[k] == 0)
        {
            d[k] = d[v] + 1;
            fa[k][0] = v;
            dfs(k);
        }
    }
}

int lca(int x, int y)
{
    if(d[x] < d[y])
    {
        swap(x, y);
    }
    for(int i = 20; i>=0; i--)
    {
        if(d[fa[x][i]] >= d[y])
            x = fa[x][i];
    }
    if(x == y)
        return x;
    for(int i = 20; i >= 0; i--)
    {
        if(fa[x][i] != fa[y][i])
        {
            x = fa[x][i];
            y = fa[y][i];
        }
    }
    return fa[x][0];
}

int main()
{
    int n, m, r;
    scanf("%d %d %d", &n, &m, &r);
    for(int i = 1; i<=n; i++)
    {
        head[i] = -1;
        d[i] = 0;
    }
    for(int i = 0; i<n-1; i++)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        add(x, y);
        add(y, x);
    }
    fa[r][0] = 0, d[r] = 1;
    dfs(r);
    for(int i = 1; i<=20; i++)
    {
        for(int j = 1; j<=n; j++)
        {
            fa[j][i] = fa[fa[j][i-1]][i-1];
        }
    }
    while(m--)
    {
        int x, y;
        scanf("%d %d", &x, &y);
        printf("%d\n", lca(x, y));
    }
    return 0;
}