1. 程式人生 > 實用技巧 >【lhyaaa】最近公共祖先LCA——倍增!!!

【lhyaaa】最近公共祖先LCA——倍增!!!

高階的演算法——倍增!!!

根據LCA的定義,我們可以知道假如有兩個節點xy,則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

的祖先也是該點 2j-1祖先的祖先

核心: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個節點;

接下來n1行,每行兩個整數x,y表示x,y之間有一條連邊;

然後一個整數Q,表示有Q個詢問;

接下來Q行每行兩個整數x,y表示詢問xy的距離。

對於全部資料,1n1051x,yn

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

第一行為兩個整數nmn表示點數,m表示詢問次數;

下來n1n−1行,每行三個整數x,y,k,表示點x和點y之間存在一條邊長度為k

再接下來m行,每行兩個整數x,y,表示詢問點x到點y的最短距離。

對於全部資料,2n1041m2×1040<k1001x,yn

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還有別的不同的求法,下次在和你們講吧(其實是我還沒學會)

這次就先到這兒吧~

拜拜~

(如果文章有不對的地方,請指出,謝謝啦^=^)