1. 程式人生 > >如何用倍增法求LCA——洛谷[P3379]題解

如何用倍增法求LCA——洛谷[P3379]題解

.org get .net ++ == main pri oid can

什麽是LCA?

LCA就是最近公共祖先的縮寫,就是假如我們有下面的一個樹。那麽這個樹上的10號結點與7號結點的LCA就是2號結點

技術分享圖片

暴力的思路

在講正解之前我們先來講講如何用暴力來解決這個問題。因為倍增就是對暴力的改進。加入我們要求7號結點與10號結點的LCA,那麽我們可以先從深度較大的結點移動到與深度較淺的結點的深度相同的地方。比如說這裏深度較大的結點是10號結點,然後我們要從10號結點跳到6號結點,然後現在這兩個結點的深度一樣了。之後我們從6號結點與7號結點同時往上跳,然後我們總會有一個時候跳到同一個位置。這個位置即使2號結點然後我們就求出了7號結點與10號結點的LCA 2 號結點

倍增的思路:

假如你天資聰慧,骨骼精奇

你一定會發現這個方法假如在樹的深度不是很大的時候還算管用,但是假如這個樹的長的類似一個鏈,那麽上面的方法就會很慢了。

那麽我們要怎麽辦呢?我們考慮倍增。我們有一個數組f[i][j]來表示一個第i號點跳 $ 2^j $ 步所到達的結點。然後我們可以得到一個遞推式:

f[i][j] = f[f[i][j - 1]][j - 1];

原因就是假如一個結點從i號結點往上跳 \(2^{j -1}\) 再跳 $ 2^{j - 1} $ 相當於從 i 號結點跳 $ 2^{j - 1} + 2 ^ {j - 1} = 2 \times 2 ^ {j - 1} = 2^j$ 步。然後這個地推式的邊界就是

f[i][0] = fa[i]; //fa[i]表示結點i的父親

然後我們還需要一個depth[i]數組,這個數組存的是每個點的深度(一個點的深度就是這個點到這個樹的根所在的點的距離,如果是一個無根樹,那麽就是到dfs起點的距離)。上面的f[i][j]數組與 depth[i] 數組都可以用 dfs 求出。代碼如下:

void dfs(int x, int dep) {
    for (register int i = 1; i < 25; ++i) 
        f[x][i] = f[f[x][i - 1]][i - 1];
    for (register int i = head[x]; i != 0 && Edge[i].s == x; i = Edge[i].nxt) {
        if (vis[Edge[i].e]) continue;
        vis[Edge[i].e] = true;
        f[Edge[i].e][0] = x;
        depth[Edge[i].e] = dep + 1;
        dfs(Edge[i].e, dep + 1);
    }
}

Ps:我是這樣存邊的

struct EDGE {
    int s; //這個邊的起點
    int e; //這個邊的終點
    int w; //這個邊的權值
    int nxt; //這個邊的下一條邊的指針
} Edge[N * 2];

之後又我們就可以開始求LCA了。

首先我們還是與暴力的思路一樣,先將從深度較大的點走到與深度較小的點的深度一樣的點。代碼如下:

if (depth[a] < depth[b]) swap(a, b); //保證a結點的深度最大
int dis = depth[a] - depth[b];       //深度差
for (register int i = 0; (1 << i) <= dis; ++i) 
    if ((1 << i) & dis) a = f[a][i];

然後我們就要從下面往上面跳,但是每次跳不能跳到從兩個點都可以跳到的地方,要不然可能會跳到LCA上面去,所以我們每次都是盡量跳到接近LCA的地方,這樣之後可以證明當每次這麽跳最後沒法跳的時候所到達的點的父親就是LCA。

接下來就是完整的求LCA的代碼:

int lca(int a, int b) {
    if (depth[a] < depth[b]) swap(a, b);
    int dis = depth[a] - depth[b];
    for (register int i = 0; (1 << i) <= dis; ++i) 
        if ((1 << i) & dis) a = f[a][i];
    if (a == b) return a;
    for (register int i = 25; i >= 0; --i) {
        if (f[a][i] != f[b][i])
            a = f[a][i], b = f[b][i];
    }
    return f[a][0];
}

一個模板題目:

題目地址:

Luogu [P3379]

題目大意:

給你一個樹,然後讓你求幾對結點的LCA

註意事項:

  1. 這個地方不能使用vector來存圖,要不然會超時
  2. 用數組存邊的時候要開結點數量兩倍的空間,因為我們存的是一個無向圖要存反向邊。

題目代碼:

#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;

const int N = 5e5 + 5;

int n, m, s;
int u, v;
int x, y;
int head[N];  //每個點連出去的第一條邊的編號
int cnt = 0;  //邊的數量
int depth[N]; //每個邊的深度
int vis[N];   //判斷每個點是否走過
int f[N][50];

struct EDGE {
    int s; //這個邊的起點
    int e; //這個邊的終點
    int w; //這個邊的權值
    int nxt; //這個邊的下一條邊的指針
} Edge[N * 2];

void add(int u, int v, int w) {
    ++cnt;
    Edge[cnt].s = u;
    Edge[cnt].e = v;
    Edge[cnt].w = w;
    Edge[cnt].nxt = head[u];
    head[u] = cnt;
}

void dfs(int x, int dep) {
    for (register int i = 1; i < 25; ++i) 
        f[x][i] = f[f[x][i - 1]][i - 1];
    for (register int i = head[x]; i != 0 && Edge[i].s == x; i = Edge[i].nxt) {
        if (vis[Edge[i].e]) continue;
        vis[Edge[i].e] = true;
        f[Edge[i].e][0] = x;
        depth[Edge[i].e] = dep + 1;
        dfs(Edge[i].e, dep + 1);
    }
}

int lca(int a, int b) {
    if (depth[a] < depth[b]) swap(a, b);
    int dis = depth[a] - depth[b];
    for (register int i = 0; (1 << i) <= dis; ++i) 
        if ((1 << i) & dis) a = f[a][i];
    if (a == b) return a;
    for (register int i = 25; i >= 0; --i) {
        if (f[a][i] != f[b][i])
            a = f[a][i], b = f[b][i];
    }
    return f[a][0];
}

int read() {
    int s = 0, w = 1;
    char ch = getchar();
    while (ch <=‘0‘ || ch > ‘9‘) {
        if (ch == ‘-‘) w = -1;
        ch = getchar();
    }
    while (ch >= ‘0‘ && ch <= ‘9‘) {
        s = s * 10 + ch - ‘0‘;
        ch = getchar();
    }
    return s * w;
}

inline void write(int x) {
    if (x < 0) putchar(‘-‘), x = -x;
    if (x > 9) write(x / 10);
    putchar(x % 10 + ‘0‘);
}

int main(int argc, char *argv[]) {
    scanf("%d %d %d", &n, &m, &s);
    for (register int i = 1; i < n; ++i) {
        scanf("%d %d", &u, &v);
        add(u, v, 1);
        add(v, u, 1);
    }
    vis[s] = true;
    dfs(s, 0);
    for (register int i = 1; i <= m; ++i) {
        scanf("%d %d", &x, &y);
        printf("%d\n", lca(x, y));
    }
    return 0;
}

如何用倍增法求LCA——洛谷[P3379]題解