樹上倍增求LCA及例題

先瞎扯幾句

樹上倍增的經典應用是求兩個節點的LCAphp

固然它的做用不只限於求LCA,還能夠維護節點的不少信息html

求LCA的方法除了倍增以外,還有樹鏈剖分、離線tarjan ,這兩種往後再講(衆人:實際上是你不會吧:unamused:。。。node

 

思想

樹上倍增嘛,顧名思義就是倍增算法

相信倍增你們都不默認,著名的rmq問題的$O(n*logn)$的解法就是利用倍增實現的數組

在樹上倍增中,咱們用post

$f[j][i]$表示第$j$號節點,跳了$2^j$步所能到達的節點url

$deep[i]$表示$i$號節點的深度spa

而後用這兩個數組瞎搞搞就能整出LCA來啦code

衆人::wrench:  :hammer: :hocho:htm

 

實現

deep&&f[i][0]

首先,$f[i][0]$(也就是一個節點的上面的節點)容易求得,只要對整棵樹進行一邊dfs就好,在dfs的時候咱們順即可以求出$deep$數組

 

for(int i=head[now];i!=-1;i=edge[i].nxt)
        if(!deep[edge[i].v])
            deep[edge[i].v]=deep[now]+1,f[edge[i].v][0]=now,dfs(edge[i].v);

 

 

 

這段代碼應該不難理解

f[j][i]

那麼咱們怎麼維護$f$數組呢?

不可貴到$f[j][i]=f[f[j][i-1]][i-1]$ 衆人:難!

其實真的不難,一張圖就能夠解釋明白啦

這句話的意思實際上是說,一個節點跳$2^j$所能到達的節點其實是跳$2^{i-1}$所能到達的節點再往上跳$2^{j-1}$步

注意$2^i=2^{i-1}+2^{i-1}$

代碼:

for(int i=1;i<=19;i++)    
    for(int j=1;j<=n;j++)
        f[j][i]=f[f[j][i-1]][i-1];

 

 

LCA

接下來要進入最核心的部分啦,

咱們如何用$deep$和$f$亂搞搞出$x$和$y$的LCA呢?

按照書上倍增算法的介紹

咱們求LCA須要分爲兩步

設$deep[x]>deep[y]$

  1. 讓$x$向上跳,跳到與$y$深度相同位置
  2. 讓$x$和$y$同時向上跳,跳到祖先相同位置

根據二進制分解什麼亂七八糟的,這麼作必定是對的,其實這個挺顯然的,yy一下就行了吧。。。

第一步

if(deep[x]<deep[y])    swap(x,y);
    for(int i=19;i>=0;i--)
        if(deep[f[x][i]]>=deep[y])
            x=f[x][i];

 

首先處理一下$x$和$y$的深度,保證$deep[x]>deep[y]$

而後儘可能讓$x$向上跳就好啦,注意這裏是能夠取到等號的

注意這裏可能會出現一種特殊狀況

這個時候他們的最近公共祖先就是$y$

 

if(x==y)    return x;

 

 第二步

同時向上跳,直到祖先相同爲止

那麼此時他們再向上跳一步所能到達的節點就是LCA啦

 

for(int i=19;i>=0;i--)
    if(f[x][i]!=f[y][i])
        x=f[x][i],y=f[y][i];
return    f[x][0];

怎麼樣?

是否是很簡單?

完整代碼

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int MAXN = 1000010;
inline void read(int &n) {
    char c = getchar(); bool flag = 0; n = 0;
    while (c < '0' || c > '9')    c == '-' ? flag = 1, c = getchar() : c = getchar();
    while (c >= '0' && c <= '9')    n = n * 10 + c - 48, c = getchar(); flag == 1 ? n = -n : n = n;
}
struct node {
    int v, nxt;
} edge[MAXN];
int head[MAXN];
int num = 1;
inline void add_edge(int x, int y) {
    edge[num].v = y;
    edge[num].nxt = head[x];
    head[x] = num++;
}
int f[MAXN][21];
int deep[MAXN];
int n, m, root;
void dfs(int now) {
    for (int i = head[now]; i != -1; i = edge[i].nxt)
        if (!deep[edge[i].v])
            deep[edge[i].v] = deep[now] + 1, f[edge[i].v][0] = now, dfs(edge[i].v);
}
void PRE() {
    for (int i = 1; i <= 19; i++)
        for (int j = 1; j <= n; j++)
            f[j][i] = f[f[j][i - 1]][i - 1];
}
int LCA(int x, int y) {
    if (deep[x] < deep[y])    swap(x, y);
    for (int i = 19; i >= 0; i--)
        if (deep[f[x][i]] >= deep[y])
            x = f[x][i];
    if (x == y)    return x;
    for (int i = 19; i >= 0; i--)
        if (f[x][i] != f[y][i])
            x = f[x][i], y = f[y][i];
    return    f[x][0];
}
int main() {

    memset(head, -1, sizeof(head));
    read(n); read(m); read(root);
    for (int i = 1; i <= n - 1; i++) {
        int x, y; read(x); read(y);
        add_edge(x, y);
        add_edge(y, x);
    }
    deep[root] = 1;
    dfs(root);
    PRE();
    for (int i = 1; i <= m; i++) {
        int x, y;
        read(x); read(y);
        printf("%d\n", LCA(x, y));
    }
    return 0;
}

 

 

  

 

 

例題

都是些入門難度的題目

洛谷P3379 【模板】最近公共祖先(LCA)

http://www.cnblogs.com/zwfymqz/p/6832524.html

POJ 1986 Distance Queries

http://www.cnblogs.com/zwfymqz/p/7791527.html

HDU 3078 Network

http://www.cnblogs.com/zwfymqz/p/7791617.html

HDU 2586 How far away ?

http://www.cnblogs.com/zwfymqz/p/7791517.html

相關文章
相關標籤/搜索