1. 程式人生 > >LCA倍增算法

LCA倍增算法

個數 Go 通過 body scanf spa void 向上 post

LCA 算法是一個技巧性很強的算法。

十分感謝月老提供的模板。

這裏我實現LCA是通過倍增,其實就是二進制優化。

任何一個數都可以有2的階數實現

例如16可以由1 2 4 8組合得到

5可以由1 2 4 組合得到

便於讀者理解 我放一道例題吧

Problem F: 挑戰迷宮

Description

小翔和小明正在挑戰一個神奇的迷宮。迷宮由n個房間組成,每個房間的編號為1~n,其中1號房間是他們倆初始位置,
所有房間一共由n-1條路連接,使得房間兩兩之間能夠相互達到(構成一棵樹),每條路的長度為Wi。
每當小翔和小明都在房間時,他們的神奇手機就能顯示兩人的位置(兩人分別在哪兩個房間),現在想請聰明的ACMer
快速地算出他們之間的最短距離。

Input

第一行輸入整數n(0<n<=100000),表示迷宮有n個房間。
接下來n-1行,每行輸入3個整數u,v,w(1<=u,v<=n,u!=v,w<=10000),表示編號為u和v的房間之間有一條長為w的路。
第n+1行輸入整數m(0<m<=100000),表示有m次詢問。
接來下m行,每行輸入2個整數u,v(1<=u,v<=n),表示小翔和小明當前所在房間的編號。

Output

對於每次詢問的輸出占一行,輸出一個整數d表示小翔和小明之間的最短距離。

Sample Input

4
1 2 1
2 3 1
1 4 1
1
3 4

Sample Output

3

這是CSUST選拔賽的一題,表示當時不會LCA 菜的摳腳 (菜是原罪啊)

註意這題時間為1S N為1e6 最短路肯定是不行的,復雜度不行。
n個點n-1條路 保證聯通 其實就是每一個點到另外一個點有唯一的路徑。
然後這就是一個非常非常裸的LCA。
 1 #include<stdio.h>
 2 #include<string.h>
 3 #include<algorithm>
 4 #include<queue>
 5 #include<vector>
 6 
 7 using namespace std;
 8 #define maxn 100010
 9 struct node {
10     int x,y;
11 node(int x=0,int y=0):x(x),y(y){}; 12 }; 13 int rk[maxn],d[maxn],p[maxn][30]; 14 vector<node>a[maxn]; 15 int n; 16 void dfs(int u,int fa,int cnt) { 17 rk[u]=cnt; 18 p[u][0]=fa; 19 int len=a[u].size(); 20 for (int i=0 ; i<len ; i++) { 21 int x=a[u][i].x; 22 if (x!=fa) { 23 d[x]=d[u]+a[u][i].y; 24 dfs(x,u,cnt+1); 25 } 26 } 27 } 28 void lca() { 29 for (int i=1 ; i<=n ; i++ ) { 30 for (int j=1 ; (1<<j)<=n ; j++) { 31 p[i][j]=-1; 32 } 33 } 34 for (int j=1 ; (1<<j)<=n ; j++) { 35 for (int i=1 ; i<=n ; i++) { 36 if (p[i][j-1]!=-1) p[i][j]=p[p[i][j-1]][j-1]; 37 } 38 } 39 } 40 int query(int x,int y) { 41 if (rk[x]<rk[y]) swap(x,y ); 42 int k; 43 for (k=1 ; (1<<(1+k))<=rk[x] ; k++); 44 for (int i= k; i>=0 ; i--) { 45 if (rk[x]-(1<<i)>=rk[y]) x=p[x][i]; 46 } 47 if (x==y) return x; 48 for (int i= k; i>=0 ; i--) { 49 if (p[x][i]!=-1 && p[x][i]!=p[y][i]){ 50 x=p[x][i]; 51 y=p[y][i]; 52 } 53 } 54 return p[x][0]; 55 } 56 int main() { 57 int q,u,v,w; 58 while(scanf("%d", &n)!=EOF) { 59 for (int i = 1; i < n; i++) { 60 scanf("%d%d%d", &u, &v, &w); 61 a[u].push_back(node(v, w)); 62 a[v].push_back(node(u, w)); 63 } 64 dfs(1, -1, 0); 65 lca(); 66 scanf("%d", &q); 67 while (q--) { 68 scanf("%d%d", &u, &v); 69 printf("%d\n", d[u]+d[v]-2*d[query(u, v)]); 70 } 71 } 72 return 0; 73 }

其中DFS(int u,int fa, int cnt)
u表示當前節點 fa為他的父親節點 cnt代表的是深度;
int rk[maxn]記錄深度 d[maxn] 記錄節點 p[maxn][30]記錄父親節點的位置
lca() 這個就是精髓所在了 第一步初始化p[i][j]=-1;
第二步就是二進制優化了 p[i][j]=p[p[i][j-1]][j-1] 表示i+2^j=i+2^(j-1)+
2^(j-1)
前面都是預處理 第三步query(int x,int y) 求x,y的公共祖先。
先判斷深度,然後算出2^k <rk[x] 的k的最大值。
if (rk[x]-(1<<i)>=rk[y]) x=p[x][i];將x的的深度向上回溯2^i
使之更接近rk[y]

for (int i= k; i>=0 ; i--) {
if (p[x][i]!=-1 && p[x][i]!=p[y][i]){
x=p[x][i];
y=p[y][i];
}
}
return p[x][0];

後面就是無腦回溯到公共祖先位置。

非常感謝月老的LCA倍增模板

以上就是我對LCA倍增算法的解析  
如果讀者還有不懂可以留言給我。


LCA倍增算法