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; }