LCA

 

参考资料:

  [1]:挑战程序设计竞赛

  [2]:Tarjan

  [3]:郭华阳《RMQ与LCA问题》 [提取码:qvx1]

 

一、浅谈LCA

  1.何为LCA

    在有根树中,两个节点u和v的公共祖先中距离根节点最远的的那个被称为最近公共祖先(LCA,Lowest Common Ancestor)。

    例如:

         

      LCA(4,5) = 2;

      LCA(4,3) = 1;

      LCA(2,5) = 2;

  2.高效计算LCA的算法

  1. 基于二分的LCA
  2. 基于Tarjan的LCA
  3. 基于RMQ的LCA  

二、浅谈高效算法

  1.基于二分的LCA

    (1)算法介绍:

      详情请看挑战程序设计竞赛p328

    (2)相关变量解释:

      maxn;.....................................................最大的节点个数

      root=1;...................................................人为规定1为根节点
      depth[maxn];..........................................某节点的深度,根节点深度为0
      dist[maxn];.............................................某节点距根节点的距离
      fa[20][maxn];..........................................fa[ i ][ j ] : 记录节点 j 沿着其父结点 向上走 2^i 步所到的节点 (超过根节点时记为-1)
      存图可以用邻接表(vector)或链式前向星(我比较倾向于链式前向星)

    (3)算法模板

 1 struct LCA
 2 {
 3     int fa[20][maxn];
 4     int dist[maxn];
 5     int depth[maxn];
 6     //一次DFS()预处理出fa[0][u],depth[u],dist[u]
 7     void DFS(int u,int f,int dis,int dep)
 8     {
 9         fa[0][u]=f;//节点u向上走2^0步来到的节点便是其父节点
10         dist[u]=dis;
11         depth[u]=dep;
12         for(int i=head[u];~i;i=G[i].next)
13         {
14             int v=G[i].to;
15             int w=G[i].w;
16             if(v != f)
17                 DFS(v,u,dis+w,dep+1);
18         }
19     }
20     void Preset()//预处理出 fa[20][maxn],关键所在
21     {
22         DFS(1,-1,0,0);
23         for(int i=0;i+1 < 20;++i)
24             for(int u=1;u <= n;++u)
25                 if(fa[i][u] == -1)
26                     fa[i+1][u]=-1;
27                 else
28                     fa[i+1][u]=fa[i][fa[i][u]];
29     }
30     int lca(int u,int v)//返回u,v的最近公共祖先
31     {
32         if(depth[u] > depth[v])
33             swap(u,v);
34         for(int i=0;i < 20;++i)
35             if((depth[v]-depth[u])>>i&1)
36                 v=fa[i][v];
37         if(u == v)
38             return u;
39         for(int k=19;k >= 0;--k)
40             if(fa[k][u] != fa[k][v])
41             {
42                 u=fa[k][u];
43                 v=fa[k][v];
44             }
45         return fa[0][u];
46     }
47 }_lca;
View Code

    对第35,36行的理解:

    假设 v 向上走 2x1,2x2,2x3,....,2xn 使得v节点来到u节点同深度的祖先节点;

    即 depth[v]-depth[u] = 2x1+2x2+2x3+....+2xn;

    令 d = depth[v]-depth[u],那么 d / 2x, d / 2x, d / 2x,...., d / 2xn 为奇数;

    具体请详见我的这篇博客?戳一下

    上述代码中的34,35,36查找v节点与u节点同一深度的祖先节点时可以优化一下(有些题因为这么一个小小的优化就过了,不然,TLE)

1 int i;
2 for(i=0;(1<<i) <= depth[v];++i);
3 i--;//从根结点(depth[root]=0)向下走,最靠近且不会超过节点v的最大步数为2^i
4 for(int k=i;k >= 0;--k)//求两者差值最少有多少个2的幂组成
5     if((depth[v]-(1<<k)) >= depth[u])//根据贪心思想,能往前走就往前走
6         v=fa[k][v];

  2.基于Tarjan的LCA

    算法流程请看参考资料[2],下面谈谈我对此算法得理解;

    对于某一结点 i ,如果询问中存在点对(u,v)使得 u∈i 的某一颗子树,v∈ i 的另一颗子树,那么Lca(u,v) = i;

    根据算法流程,由节点 i 向下搜索其中一颗子树时,假设为u所在的子树,但此前并没有搜索到v所在的子树,

    当来到u节点时,此时vis[ v ] =false,并将fa[u]=其父亲节点,vis[u]=true;

    在往上回溯过程中,再一次来到 i 节点,根据并查集可得 fa[u]=i ;

    继续执行搜索过程,来到另一颗子树,假设为v所在的子树,当来到v节点时,vis[ u ]=true,说明之前搜索过u节点,那么

    Lca(u,v) = fa[u] = i;

  3.基于ST()的LCA

    算法介绍请看参考资料[3],讲的超详细;

    我的算法模板:

 1 /**
 2     LCA:DFS+ST()
 3 */
 4 #include<iostream>
 5 #include<cstdio>
 6 #include<cmath>
 7 #include<cstring>
 8 using namespace std;
 9 #define mem(a,b) memset(a,b,sizeof(a))
10 #define ll long long
11 const int maxn=1e5+50;
12 
13 int n,m;
14 int num;
15 int head[maxn];
16 ll dis[maxn];
17 struct Edge
18 {
19     int to;
20     ll w;
21     int next;
22 }G[maxn<<1];
23 void addEdge(int u,int v,ll w)
24 {
25     G[num]=Edge{v,w,head[u]};
26     head[u]=num++;
27 }
28 struct LCA
29 {
30     /**
31         个人习惯数组下标从1开始
32         此模板的下标从1开始
33     */
34     int vs[maxn<<1];///欧拉序列
35     int dep[maxn<<1];///欧拉序列对应的深度序列
36     int pos[maxn];///pos[i]:节点i在欧拉序列中第一次出现的位置
37     int cnt;
38     int logTwo[maxn<<1];///logTwo[i]=log2(i)
39     int dp[maxn<<1][20];
40     void DFS(int u,int f,int depth,ll dist)
41     {
42         vs[++cnt]=u;
43         dep[cnt]=depth;
44         pos[u]=cnt;
45         dis[u]=dist;
46         for(int i=head[u];~i;i=G[i].next)
47         {
48             int v=G[i].to;
49             int w=G[i].w;
50             if(v == f)
51                 continue;
52             DFS(v,u,depth+1,dist+w);
53             vs[++cnt]=u;
54             dep[cnt]=depth;
55         }
56     }
57     void ST()
58     {
59         ///预处理出dp[i][j],logTwo[i]
60         logTwo[0]=-1;
61         for(int i=1;i <= cnt;++i)
62         {
63             dp[i][0]=i;
64             ///111(2)->1000(2):(111)&(1000)=0,logTwo[1000]=logTwo[111]+1
65             logTwo[i]=((i&(i-1)) == 0) ? logTwo[i-1]+1:logTwo[i-1];
66         }
67         for(int k=1;k <= logTwo[cnt];++k)
68             for(int i=1;i+(1<<k)-1 <= cnt;++i)
69                 if(dep[dp[i][k-1]] > dep[dp[i+(1<<(k-1))][k-1]])
70                     dp[i][k]=dp[i+(1<<(k-1))][k-1];
71                 else
72                     dp[i][k]=dp[i][k-1];
73     }
74     void lcaInit(int root)
75     {
76         cnt=0;
77         DFS(root,root,0,0);
78         ST();
79     }
80     int lca(int u,int v)///返回u,v的公共祖先
81     {
82         u=pos[u];
83         v=pos[v];
84         if(u > v)
85             swap(u,v);
86         int k=logTwo[v-u+1];
87         if(dep[dp[u][k]] > dep[dp[v-(1<<k)+1][k]])
88             return vs[dp[v-(1<<k)][k]];
89         else
90             return vs[dp[u][k]];
91     }
92 }_lca;
93 void Init()///一定要记得Init()
94 {
95     num=0;
96     mem(head,-1);
97 }
View Code

 

转载于:https://www.cnblogs.com/violet-acmer/articles/9745083.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值