LCA倍增算法

Posted Fitz

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LCA倍增算法相关的知识,希望对你有一定的参考价值。

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倍增算法的主要内容,如果未能解决你的问题,请参考以下文章

LCA(倍增在线算法) codevs 2370 小机房的树

LCA

求LCA的倍增算法

算法笔记--lca倍增算法

LCA回顾(主要针对倍增算法)

LCA算法解析-Tarjan&倍增