LCA 算法是一个技巧性很强的算法。
十分感谢月老提供的模板。
这里我实现LCA是通过倍增,其实就是二进制优化。
任何一个数都可以有2的阶数实现
例如16可以由1 2 4 8组合得到
5可以由1 2 4 组合得到
便于读者理解 我放一道例题吧
Problem F: 挑战迷宫
Description
小翔和小明正在挑战一个神奇的迷宫。迷宫由n个房间组成,每个房间的编号为1~n,其中1号房间是他们俩初始位置,
所有房间一共由n-1条路连接,使得房间两两之间能够相互达到(构成一棵树),每条路的长度为Wi。
每当小翔和小明都在房间时,他们的神奇手机就能显示两人的位置(两人分别在哪两个房间),现在想请聪明的ACMer
快速地算出他们之间的最短距离。
所有房间一共由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),表示小翔和小明当前所在房间的编号。
接下来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倍增算法的解析
如果读者还有不懂可以留言给我。