【lhyaaa】最近公共祖先LCA——倍增!!!
高階的演算法——倍增!!!
根據LCA的定義,我們可以知道假如有兩個節點x和y,則LCA(x,y)是 x 到根的路 徑與 y 到根的路徑的交匯點,同時也是 x 和 y 之間所有路徑中深度最小的節 點,所以,我們可以用遍歷路徑的方法求 LCA。
但想想都知道啦,這種遍歷的方法肯定too slow,最壞情況時可達到O(n),資料大點兒,就光榮TLE了。
所以我們高階的化身——倍增演算法就出現了!
談談倍增——
倍增簡單來講就是兩個點跳到同一高度後,再一起往上跳,直到跳到一個共同的點,就能找到它們的最近公共祖先啦
具體來說,分為以下幾個步驟——
首先,我們得找幾個幫手——
陣列 | 解釋 |
f[][] | 預處理倍增f[v][i] |
d[][] | 記錄節點深度 |
vis[][] | 判重,以防搜到節點的爸爸 |
head[][] | 存圖時用der~(不懂戳) |
dis[][] |
一個節點到根的最短距離 (有權值記得加權值) |
Step1: 預處理每個結點的深度和該節點的父親節點
我們用 d 陣列來表示每個結點的深度,設節點 1 為根,d[1]=0,初始化 f[v][0]=u, 表示 v 的 20 的祖先節點為 u。
tips:如果樹是無根樹時需要把它變成有根數(隨便找個點就OK)
Step2: 預處理倍增f[v][i],2j
核心:f[u][j] = f [f [u][j-1]][j-1]
不懂的盆友,我們來舉個例子——
我們有這樣的一張圖,我們對它進行預處理就會變成這樣——
*圖源戳
還不懂的,就手動模擬一波~
Step3: 查詢時,深度大的先往上跳,直至與深度小的點再同一層。若此時兩節點相同直接返回此節點(在同一條鏈上),如果不同,就利用倍增的思想,同時讓 x 和 y 向上找,直到找到深度相等且最小的 x 和 y 的祖先 x′,y′,滿足 x′≠y′。此時他們的父結點即為 x 和 y 的最近公共祖先 LCA。
倍增解法是 LCA 問題的線上解法,整體時間複雜度是 O(VlogV+QlogV),其 中 Q 是總查詢次數。
看起來是不是還不錯?那我們來看道題吧~
談談題目——
Description
給定一棵n個點的樹,Q個詢問,每次詢問點x到點y兩點之間的距離。
Input
第一行一個正整數n,表示這棵樹有n個節點;
接下來n−1行,每行兩個整數x,y表示x,y之間有一條連邊;
然後一個整數Q,表示有Q個詢問;
接下來Q行每行兩個整數x,y表示詢問x到y的距離。
對於全部資料,1≤n≤105,1≤x,y≤n。
Output
輸出Q行,每行表示每個詢問的答案。
Example
stdin
6
1 2
1 3
2 4
2 5
3 6
2
2 6
5 6
stdout
3
4
*原題戳
Thinking
說實話這題沒什麼好講的叭,就是求LCA就OK啦,板子題耶!
上程式碼——
1 #include<bits/stdc++.h> 2 using namespace std; 3 int t,q,tot; 4 int dis[1100010]; 5 int vis[1100010]; 6 struct node{ 7 int nxt,to; 8 }e[1100010]; 9 int head[1100010]; 10 int f[1100010][20]; 11 int d[1100010]; 12 13 void add(int u, int v){ 14 e[++tot].to = v; 15 e[tot].nxt=head[u]; 16 head[u] = tot; 17 } 18 19 void dfs(int u, int fa, int dep){ //預處理深度,倍增陣列 20 vis[u] = 1; 21 f[u][0] = fa; //初始化記錄父節點 22 d[u] = dep; 23 for(int j = 1; j <= 19; j++) f[u][j] = f[f[u][j-1]][j-1]; 24 for(int i = head[u]; i; i = e[i].nxt){ 25 int v = e[i].to; 26 if(vis[v]) continue; 27 dfs(v,u,dep+1); 28 } 29 } 30 31 int lca(int x, int y){ //倍增求 LCA 32 if(d[x] > d[y]) swap(x,y); 33 for(int i = 19; i >= 0; i--){ 34 if(d[f[y][i]] >= d[x]) y = f[y][i]; //深度較深節點先往上跳至同一深度 35 if(x == y) return x; 36 } 37 for(int i = 19; i >= 0; i--){ 38 if(f[x][i] != f[y][i]) x = f[x][i], y= f[y][i]; 39 } 40 return f[x][0]; 41 } 42 43 int main(){ 44 scanf("%d",&t); 45 tot = 0; 46 for(int i = 1; i < t; i++){ 47 int u,v; 48 scanf("%d%d",&u,&v); 49 add(u,v),add(v,u); 50 } 51 dfs(1,0,0); 52 cin >> q; 53 for(int i = 1; i <= q; i++){ 54 int x,y; 55 scanf("%d%d",&x,&y); 56 printf("%d\n",d[x]+d[y]-2*d[lca(x,y)]); //兩個節點到跟的距離減去重複計算的它們公共祖先到跟的距離 57 } 58 }
解釋一下這個東西 d[x] + d[y] - 2 * d[lca(x,y)];
畫張圖——
d[x] 和 d[y] 就是紅色那兩條東西
d[lca(x,y)] 就是藍色那條
d[x] + d[y] - 2*d[lca(x,y)] 就是綠色那條啦~
當然這是沒有權值得時候,我們預設深度差不多等於距離,但有了權值就不一樣了。
我們再來看一道板子題——
Description
給出nn個點的一棵樹,多次詢問兩點之間的最短距離。
注意:邊是雙向的。
Input
第一行為兩個整數n和m。n表示點數,m表示詢問次數;
下來n−1n−1行,每行三個整數x,y,k,表示點x和點y之間存在一條邊長度為k;
再接下來m行,每行兩個整數x,y,表示詢問點x到點y的最短距離。
對於全部資料,2≤n≤104,1≤m≤2×104,0<k≤100,1≤x,y≤n。
Output
輸出m行。對於每次詢問,輸出一行。
Example
stdin1
2 2 1 2 100 1 2 2 1
stdout1
100 100
stdin2
3 2 1 2 10 3 1 15 1 2 3 2
stdout2
10 25
*原題戳
1 #include<bits/stdc++.h> 2 using namespace std; 3 int n,m,q,tot; 4 int dis[1100010]; 5 int vis[1100010]; 6 struct node{ 7 int nxt,to; 8 int w; 9 }e[1100010]; 10 int head[1100010]; 11 int f[1100010][20]; 12 int d[1100010]; 13 14 void add(int u, int v, int w){ 15 e[++tot].to = v; 16 e[tot].w = w; 17 e[tot].nxt=head[u]; 18 head[u] = tot; 19 } 20 21 void dfs(int u, int fa, int dep){ 22 vis[u] = 1; 23 f[u][0] = fa; 24 d[u] = dep; 25 for(int j = 1; j <= 19; j++) f[u][j] = f[f[u][j-1]][j-1]; 26 for(int i = head[u]; i; i = e[i].nxt){ 27 int v = e[i].to; 28 if(vis[v]) continue; 29 dis[v] = dis[u] + e[i].w; 30 dfs(v,u,dep+1); 31 } 32 } 33 34 int lca(int x, int y){ 35 if(d[x] > d[y]) swap(x,y); 36 for(int i = 19; i >= 0; i--){ 37 if(d[f[y][i]] >= d[x]) y = f[y][i]; 38 if(x == y) return x; 39 } 40 for(int i = 19; i >= 0; i--){ 41 if(f[x][i] != f[y][i]) x = f[x][i], y= f[y][i]; 42 } 43 return f[x][0]; 44 } 45 int main(){ 46 scanf("%d%d",&n,&m); 47 for(int i = 1; i < n; i++){ 48 int u,v,w; 49 scanf("%d%d%d",&u,&v,&w); 50 add(u,v,w); 51 add(v,u,w); 52 } 53 dfs(1,0,1); 54 for(int i = 1; i <= m; i++){ 55 int x,y; 56 scanf("%d%d",&x,&y); 57 printf("%d\n",dis[x]+dis[y]-2*dis[lca(x,y)]); 58 } 59 }
注意到沒有?
這一道題是有權值的,所以最後輸出的時候輸出的是 dis[x] + dis[y] - 2 * dis[lca(x,y)]
總結一下
其實LCA還有別的不同的求法,下次在和你們講吧(其實是我還沒學會)
這次就先到這兒吧~
拜拜~
(如果文章有不對的地方,請指出,謝謝啦^=^)