博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
LCA
阅读量:5138 次
发布时间:2019-06-13

本文共 5247 字,大约阅读时间需要 17 分钟。

 

参考资料:

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

  [2]:

  [3]: [提取码: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                 else28                     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<
<= depth[v];++i);3 i--;//从根结点(depth[root]=0)向下走,最靠近且不会超过节点v的最大步数为2^i4 for(int k=i;k >= 0;--k)//求两者差值最少有多少个2的幂组成5 if((depth[v]-(1<
= 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
5 #include
6 #include
7 #include
8 using namespace std; 9 #define mem(a,b) memset(a,b,sizeof(a))10 #define ll long long11 const int maxn=1e5+50;12 13 int n,m;14 int num;15 int head[maxn];16 ll dis[maxn];17 struct Edge18 {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 LCA29 {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]+165 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<
<= 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 else72 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<
View Code

 

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

你可能感兴趣的文章
【题解】[P4178 Tree]
查看>>
QML学习笔记之一
查看>>
WPF中实现多选ComboBox控件
查看>>
ionic2+ 基础
查看>>
MyBaits动态sql语句
查看>>
用户空间与内核空间,进程上下文与中断上下文[总结]
查看>>
JAVA开发环境搭建
查看>>
django迁移数据库错误
查看>>
Data truncation: Out of range value for column 'Quality' at row 1
查看>>
字符串处理
查看>>
ad logon hour
查看>>
罗马数字与阿拉伯数字转换
查看>>
Eclipse 反编译之 JadClipse
查看>>
距离公式汇总以及Python实现
查看>>
Linux内核态、用户态简介与IntelCPU特权级别--Ring0-3
查看>>
第23月第24天 git命令 .git-credentials git rm --cached git stash clear
查看>>
java SE :标准输入/输出
查看>>
[ JAVA编程 ] double类型计算精度丢失问题及解决方法
查看>>
好玩的-记最近玩的几个经典ipad ios游戏
查看>>
Sql Server 中由数字转换为指定长度的字符串
查看>>