LCA

LCA,全稱爲Lowest Common Ancestor, 即最近公共祖先。這是對於有根樹而言的,兩個節點u, v的公共祖先中距離最近的那個被稱爲最近公共祖先(這解釋。。真通俗。。。)咱們來看個圖:php

 

4和7的LCA是2,5和6的LCA是1,2和5的LCA是2。算法

最笨的實現方法就是;數組

對於同一深度的點,一個個往上走知道走到相同的點爲止;不一樣深度的點轉化爲同一深度的點再往上走!spa

複雜度爲O(dep[u] + dep[v] - 2 * dep[c])(u, v表示要查詢的點,c爲u, v的LCA)blog

代碼以下:get

#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100000 + 5;

vector<int> G[N];

int rt;//表示根,即root

int dsu[N], dep[N];

//dsu數組表示父親,dep表示深度 

void dfs(int v, int p, int d){
    dsu[v] = p;
    dep[v] = d;
    for(int i = 0; i < G[v].size(); i ++)
        if(G[v][i] != p) dfs(G[v][i], v, d + 1);
    //G[v][i] != p的緣由是讀入的時候是無向,因此存在反向邊,例在處理節點v的時候,假設dsu[v] = u, 則存在i,使得G[v][i] = u,這樣會致使在兩個點中一直循環,致使陷入死循環。
}

void init(){
    dfs(rt, -1, 0);
}

int lca(int u, int v){
    if(dep[u] > dep[v]) swap(u, v);
    while(dep[v] > dep[u]) v = dsu[v];
    //讓u, v處於同一深度。
    while(u != v){
        u = dsu[u];
        v = dsu[v];
    }
    return u;
}

int n, q, a, b;

int main(){
    //咱們以n個點n - 1條邊,q次詢問爲例
    while(scanf("%d%d", &n, &q) == 2){
        for(int i = 1; i <= n; i ++)G[i].clear();
        for(int i = 1; i < n; i ++){
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        rt = 1;//以1爲根
        init();
        while(q --){
            scanf("%d%d", &a, &b);
            printf("%d\n", lca(a, b));
        }
    }
    return 0;
}

咱們發現若是有n個點最壞的狀況下有O(n)的複雜度,若是屢次查詢複雜度確定會爆掉,因此咱們必需要有高效的算法。string

實現LCA的高效算法有二種,分別是倍增法和RMQ法。it

一.倍增法io

 咱們首先這們想若是相同深度的兩個節點u, v當往上走k步的時候走到同一節點,那麼往上走k + 1步仍是同一節點,k + 2步也是, k + k即2k步也是。咱們把上面的dsu數組變成2維數組dsu[k][v]表示節點v往上走2k步所走到的節點。那麼dsu[k + 1][v] = dsu[k][dsu[k][v]];這樣咱們就能夠經過二分來查找他們的LCA了,每次查詢複雜度爲O(logn), 預處理爲O(nlogn)。class

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 10000 + 5;

vector<int> G[N];


int rt, a, b, c, q, n, m, T;

int dsu[40][N], dep[N];

void dfs(int v, int p, int d){
    dsu[0][v] = p;
    dep[v] = d;
    for(int i = 0; i < G[v].size(); i ++)
        if(G[v][i] != p) dfs(G[v][i], v, d + 1);
}

void init(){
    dfs(rt, -1, 0);
    for(int k = 0; k + 1 < 32; k ++){
        for(int v = 1; v <= n; v ++){
            if(dsu[k][v] < 0)dsu[k + 1][v] = -1;//根節點的父親設爲-1
            else dsu[k + 1][v] = dsu[k][dsu[k][v]];
        }
    }
}

int lca(int u, int v){
    if(dep[u] > dep[v]) swap(u, v);
    for(int k = 0; k < 32; k ++){
        if((dep[v] - dep[u]) >> k & 1)
            v = dsu[k][v];
    }
    if(u == v)return u;
    for(int k = 31; k >= 0; k --){
        if(dsu[k][u] != dsu[k][v]){
            u = dsu[k][u];
            v = dsu[k][v];
        }
    }
    return dsu[0][u];
}


int main(){
    while(scanf("%d%d", &n, &q) == 2){
        for(int i = 1; i <= N; i ++)G[i].clear();
        for(int i = 1; i < n; i ++){
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        rt = 1;
        init();
        while(q --){
            scanf("%d%d", &a, &b);
            printf("%d\n", lca(a, b));
        }
    }
    return 0;
}

二.RMQ法

其實這裏還涉及到另一個東西,叫作dfs序。是指你用dfs遍歷一棵樹時,每一個節點會按照遍歷到的前後順序獲得一個序號。而後你用這些序號,能夠把整個遍歷過程表示出來。以下圖:

 如上圖所示,則整個遍歷過程爲1 2 4 2 5 7 5 8 5 2 1 3 6 3 1

咱們將他保存在一個vs數組,並開個id數組記錄第一次在vs中出現的下標,例如id[1] = 1, id[4] = 3;

並用dep數組儲存vs數組中每一個數的深度,例如dep[2] = dep[4] = 1(vs數組中第2個和第4個都是2,2的深度爲2)。

而LCA(u, v)就是第一次訪問u以後到第一次訪問v以前所通過頂點中離根最近的那個。假設id[u] <= id[v],那麼LCA(u, v) = vs[t] t爲id[u]與id[v]中dep最小的那一個。

而這個不就至關於求區間的RMQ嗎?

附上代碼:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100000 + 5;

vector<int>G[N];

int rt, n, m, a, b, c, q;

int vs[N * 2 - 1], dep[N * 2 - 1], id[N], sum[N];

int dp[N][40];

struct RMQ{
    int log2[N];
    void init(int n){
        log2[0] = -1;
        for(int i = 1; i <= n; i ++)log2[i] = log2[i >> 1] + 1;
        for(int i = 1; i <= n; i ++)dp[i][0] = vs[i];
        for(int j = 1; j <= log2[n]; j ++){
            for(int i = 1; i + (1 << j) <= n + 1; i ++){
                int ca = dp[i][j - 1];
                int cb = dp[i + (1 << (j - 1))][j - 1];
                if(vs[ca] < vs[cb]) dp[i][j] = ca;
                else dp[i][j] = cb;
            }
        }
    }
    int query(int ls, int rs){
        int k = log2[rs - ls + 1];
        int ca = dp[ls][k];
        int cb = dp[rs - (1 << k) + 1][k];
        if(vs[ca] < vs[cb]) return ca;
        else return cb;
    }
}rmq;

void dfs(int v, int p, int d, int& k){
    //printf("v = %d\n", v);
    id[v] = k;
    vs[k] = v;
    dep[k ++] = d;
    for(int i = 0; i < G[v].size(); i ++){
        if(G[v][i] != p){
            dfs(G[v][i], v, d + 1, k);
            vs[k] = v;
            dep[k ++] = d;
        }
    }
}

void init(int V){
    int k = 1;
    dfs(rt, -1, 0, k);
    rmq.init(V * 2 - 1);
}

int lca(int u, int v){
    return vs[rmq.query(min(id[u], id[v]), max(id[u], id[v]))];
}

void print(){
    for(int i = 0; i < 2 * n; i ++) printf("vs[%d] = %d\n", i, vs[i]);
    for(int i = 1; i <= n; i ++) printf("id[%d] = %d\n", i, id[i]);
    for(int i = 1; i <= n; i ++) printf("dep[%d] = %d\n", i, dep[i]);
    for(int i = 1; i <= n; i ++) printf("sum[%d] = %d\n", i, sum[i]);
}

int main(){
    while(scanf("%d%d", &n, &q) == 2){
        for(int i = 1; i <= n; i ++)G[i].clear();
        for(int i = 1; i < n; i ++){
            scanf("%d%d", &a, &b);
            G[a].push_back(b);
            G[b].push_back(a);
        }
        rt = 1;
        init(n);
        //print();
        while(q --){
            scanf("%d%d", &a, &b);
            printf("%d\n", lca(a, b));
        }
    }
    return 0;
}

 LCA的應用

LCA能夠用來求樹上的兩個頂點之間的權值和,讓任意一個點做爲根節點,設sum[u]爲頂點rt到u的權值和,那麼u到v的權值和就是sum[u] - sum[lca(u, v)] + sum[v] - sum[lca(u, v)]。

來看一道題: 傳送門

 

附上兩種方法的代碼:

1.倍增法:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 40000 + 5;

vector<int> G[N];

vector<int> E[N];

int rt, a, b, c, q, n, m, T;

int dsu[40][N], dep[N], sum[N];

void dfs(int v, int p, int d){
    dsu[0][v] = p;
    dep[v] = d;
    for(int i = 0; i < G[v].size(); i ++)
        if(G[v][i] != p){
            sum[G[v][i]] = sum[v] + E[v][i];
            dfs(G[v][i], v, d + 1);
        }
}

void init(){
    dfs(rt, -1, 0);
    for(int k = 0; k + 1 < 32; k ++){
        for(int v = 1; v <= n; v ++){
            if(dsu[k][v] < 0)dsu[k + 1][v] = -1;
            else dsu[k + 1][v] = dsu[k][dsu[k][v]];
        }
    }
}

int lca(int u, int v){
    if(dep[u] > dep[v]) swap(u, v);
    for(int k = 0; k < 32; k ++){
        if((dep[v] - dep[u]) >> k & 1)
            v = dsu[k][v];
    }
    if(u == v)return u;
    for(int k = 31; k >= 0; k --){
        if(dsu[k][u] != dsu[k][v]){
            u = dsu[k][u];
            v = dsu[k][v];
        }
    }
    return dsu[0][u];
}


int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &q);
        for(int i = 1; i <= N; i ++)G[i].clear(), E[i].clear();
        for(int i = 1; i < n; i ++){
            scanf("%d%d%d", &a, &b, &c);
            G[a].push_back(b);
            G[b].push_back(a);
            E[a].push_back(c);
            E[b].push_back(c);
        }
        rt = 1;
        init();
        while(q --){
            scanf("%d%d", &a, &b);
            c = lca(a, b);
            printf("%d\n", sum[a] + sum[b] - 2 * sum[c]);
        }
    }
    return 0;
}

2RMQ + dfs序:

#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
const int N = 100000 + 5;

vector<int>G[N];

vector<int>E[N];

int rt, n, m, a, b, c, q;

int vs[N * 2 - 1], dep[N * 2 - 1], id[N], sum[N];

int dp[N][40];

struct RMQ{
    int log2[N];
    void init(int n){
        log2[0] = -1;
        for(int i = 1; i <= n; i ++)log2[i] = log2[i >> 1] + 1;
        for(int i = 1; i <= n; i ++)dp[i][0] = vs[i];
        for(int j = 1; j <= log2[n]; j ++){
            for(int i = 1; i + (1 << j) <= n + 1; i ++){
                int ca = dp[i][j - 1];
                int cb = dp[i + (1 << j)][j - 1];
                if(vs[ca] < vs[cb]) dp[i][j] = ca;
                else dp[i][j] = cb;
            }
        }
    }
    int query(int ls, int rs){
        int k = log2[rs - ls + 1];
        int ca = dp[ls][k];
        int cb = dp[rs - (1 << k) + 1][k];
        if(vs[ca] < vs[cb]) return ca;
        else return cb;
    }
}rmq;

void dfs(int v, int p, int d, int& k){
    //printf("v = %d\n", v);
    id[v] = k;
    vs[k] = v;
    dep[k ++] = d;
    for(int i = 0; i < G[v].size(); i ++){
        if(G[v][i] != p){
            sum[G[v][i]] = sum[v] + E[v][i];
            dfs(G[v][i], v, d + 1, k);
            vs[k] = v;
            dep[k ++] = d;
        }
    }
}

void init(int V){
    int k = 1;
    sum[0] = sum[1] = 0;
    dfs(rt, -1, 0, k);
    rmq.init(V * 2 - 1);
}

int lca(int u, int v){
    return vs[rmq.query(min(id[u], id[v]), max(id[u], id[v]))];
}

void print(){
    for(int i = 0; i < 2 * n; i ++) printf("vs[%d] = %d\n", i, vs[i]);
    for(int i = 1; i <= n; i ++) printf("id[%d] = %d\n", i, id[i]);
    for(int i = 1; i <= n; i ++) printf("dep[%d] = %d\n", i, dep[i]);
    for(int i = 1; i <= n; i ++) printf("sum[%d] = %d\n", i, sum[i]);
}

int main(){
    int T;
    scanf("%d", &T);
    while(T--){
        scanf("%d%d", &n, &q);
        for(int i = 1; i <= n; i ++)G[i].clear(), E[i].clear();
        for(int i = 1; i < n; i ++){
            scanf("%d%d%d", &a, &b, &c);
            G[a].push_back(b);
            G[b].push_back(a);
            E[a].push_back(c);
            E[b].push_back(c);
        }
        rt = 1;
        init(n);
        //print();
        while(q --){
            scanf("%d%d", &a, &b);
            c = lca(a, b);
            //printf("a = %d, b = %d, c = %d\n", a, b, c);
            printf("%d\n", sum[a] + sum[b] - 2 * sum[c]);
        }
    }
    return 0;
}
相關文章
相關標籤/搜索