LCA问题

一.概述:

  在图论与计算科学中,两个节点 与 有向无环图directed acyclic graph , DAG )或中的最近公共祖先(Lowest common anccestor , LCA ) 是这两个节点 v 与 w 的深度最深的祖先。我们定义,该深度最深的节点为 v 与 w 的最近公共最先,即LCA 。

例如,在下图中

LCA ( A , B ) = F , LCA ( A , G ) = C , LCA ( B , D ) = C , LCA ( C , G ) = C ;

[ATTENTION]:两个节点的LCA在两点间的路径上。

二.求解方法

  方法一:

    将LCA问题转化为RMQ问题(区间最值问题)。

    1).从任意一个节点开始,对图进行深度优先遍历,记录每个节点的欧拉序,深度,第一次被遍历fst[]时间等信息。

    2).将每个节点的深度按照欧拉序列加入一个数组中,即每次经过一个节点时,都将其加入数组。

    3).如果fst[ v ] < fst[ u ] ,则LCA( v , u ) = RMQ ( fst[ v ] , fst[ u ] )

       如果fst[ v ] > fst[ u ] ,则LCA( v , u ) = RMQ ( fst[ u ] , fst[ v ] )

    以上就是主要思路,下面举个栗子:

    

    

    当查询A 与 D 的 LCA 时 , LCA ( A , D ) = RMQ ( fst[ D ] , fst[ A ] )

     即,区间 [ fst[ D ] , fst[ A ] ] 的深度最小值 , 这段区间[ 2 , 7 ] ,(2 ,1 ,2 ,1 ,2 ,3)中最小值是1,1对C应的节点是C,则A,D的LCA是C 。

     [ATTENTION]:区间最值的关键字是深度.

     那么现在的问题就是解决RMQ问题,通常使用ST算法(RMQ问题之ST算法)或线段树解决,本文使用线段树解决这个问题。

     核心代码&注释:

 inline void Add_Edge ( int x , int y , int _val ){//邻接表建图
e[ ++cnt ].to = y ;
e[ cnt ].val = _val ;
e[ cnt ].next = head[ x ] ;
head[ x ] = cnt ;
} void Build_Tree ( int x , int y , int i ) {//线段树建树
tr[ i ].l = x ; tr[ i ].r = y ;
if ( x==y ) tr[ i ].mintr = dep[ x ] , tr[ i ].pos = x ;//按照深度建树
else {
QAQ mid = ( tr[ i ].l + tr[ i ].r ) >> ;
Build_Tree ( x , mid , i<<);
Build_Tree ( mid+ , y , i<<|);
if (tr[i<<].mintr > tr[i<<|].mintr )//tr[].mintr表示这个区间最小值 ,tr[].pos表示最小值所在位置
tr[ i ].pos = tr[i<<|].pos,tr[ i ].mintr = tr[i<<|].mintr;
else
tr[ i ].pos = tr[ i<< ].pos,tr[ i ].mintr = tr[ i<< ].mintr;
} } void DFS ( int x , int depth ) {
vis[ x ] = true ;
ver[ ++dfs_num ] = x ; //欧拉序
fst[ x ] = dfs_num ; //第一次出现位置
dep[ dfs_num ] = depth ;//该节点深度
for ( int i=head[ x ] ; i ; i=e[i].next ) {
int temp = e[ i ].to ;
if ( !vis[ temp ] ){
DFS ( temp , depth + ) ;
ver[ ++dfs_num ] = x ;
dep[ dfs_num ] = depth ;
}
}
} void Query_Min ( int q , int w , int i ) {
if(q <= tr[i].l && w >= tr[i].r ){
if (tr[ i ].mintr < min_val ){
min_val = tr[i].mintr ;// 记录最小值
min_pos = tr[i].pos ;// 记录最小值所在位置
}
}
else {
QAQ mid = (tr[i].l + tr[i].r ) >> ;
if(q > mid) {
Query_Min ( q , w , i << | );
}
else if(w <= mid) {
Query_Min ( q , w , i << );
}
else {
Query_Min ( q , w , i << ) ;
Query_Min ( q , w , i << | );
}
}
} int LCA ( int x , int y ) {
int px = fst[ x ] , py = fst[ y ] , tmp ;
min_val = INF ;//初始化
if ( py < px ) swap ( px , py ) ;
Query_Min ( px , py , ) ;
return ver[ min_pos ] ;//最小值在欧拉序中对应节点即为LCA
}
int main ( ) {
int N ,M ;
scanf ("%d",&N);
for ( int i= ; i<=N- ; ++i ) {
int _x , _y , __ ;
scanf("%d %d %d" , &_x , &_y ,&__ ) ;
Add_Edge ( _x , _y , __ ) ;
Add_Edge ( _y , _x , __ ) ;
}
DFS ( , ) ;
Build_Tree ( , dfs_num , ) ;
DEBUG_( dfs_num ) ;
scanf ("%d",&M);
for ( int i= ; i<=M ; ++i ) {
int u , v ;
scanf ( "%d%d" , &u , &v ) ;
printf ("%d",LCA ( u , v ) ) ;
putchar('\n');
}
return ;
}

LCA->RMQ

————————————————分割线————————————————

  方法二:

     倍增算法求LCA.

      倍增算法的核心在于father[][]数组,father[ i ] [ j ] 表示从节点 i 开始,向上第2j个节点编号。

      可以通过以下的式子推出

father[ x ][ i ] = father[ father[ x ][ i - 1 ] ][ i - 1 ] ;

      如下图,求节点 7 与 节点 15 的LCA 。

 首先找到深度较深的节点15 , 让该节点向上移动,因为LCA一定在两节点的路径上。通过倍增思想,让该节点向上移动,使两个节点的深度相同。

if ( dep[ x ] < dep[ y ] )gswap( x , y ) ;
int t = dep[ x ] - dep[ y ] ;
for ( int i= ; i<= ; ++i )
if( ( << i ) & t )
x = father[ x ][ i ] ;

这时,两个节点深度相同,仍然通过倍增思想,让两节点以相同的速率向上跳,跳到两个节点的父节点恰好相等

if( x == y ) return x ;//蜜汁特判
for ( int i= ; i>= ; --i ) {
if ( father[ x ][ i ] == father[ y ][ i ] ) continue ;//跳多了,换一个小一点的值
x = father[ x ][ i ] ;//两个节点以相同速率向上跳
y = father[ y ][ i ] ;//
}

这时,两个节点的父节点就是LCA,直接返回父节点即可,算法结束。

[ATTENTION] : 这里需要加一个特判,如果两个节点调到同一位置直接返回。

核心代码&注释:

 inline void gswap ( int &x , int &y ) { int temp = x ; x = y ; y = temp ; }

 int cnt ;

 void Add_Edge ( const int x , const int y , const int val ) {//建边
e[ ++cnt ].to = y ;
e[ cnt ].val = val ;
e[ cnt ].next = head[ x ] ;
head[ x ] = cnt ;
} void DFS ( int x ) {
vis[ x ] = true ;
for ( int i= ; i<= ; ++i ) {
if ( dep[ x ] < ( << i ) ) break ;
father[ x ][ i ] = father[ father[ x ][ i - ] ][ i - ] ;//father数组的递推
}
for ( int i=head[ x ] ; i ; i=e[ i ].next ) {//图的DFS
int temp = e[ i ].to ;
if ( vis[ temp ] )continue ;
else {
Dis [ temp ] = Dis [ x ] + e[ i ].val ;
father[ temp ][ ] = x ;
dep[ temp ] = dep[ x ] + ;
DFS ( temp ) ;
}
}
}
int LCA ( int x , int y ) {
if ( dep[ x ] < dep[ y ] )gswap( x , y ) ;
int t = dep[ x ] - dep[ y ] ;//深度差
for ( int i= ; i<= ; ++i ) if( ( << i ) & t ) x = father[ x ][ i ] ;//让深度较大的节点跳至深度相等
if( x == y ) return x ;//蜜汁特判
for ( int i= ; i>= ; --i ) {//两个节点同速率向上跳 到父亲恰好相等
if ( father[ x ][ i ] == father[ y ][ i ] ) continue ;
x = father[ x ][ i ] ; y = father[ y ][ i ] ;
}
return father[ x ][ ] ;// 返回父节点
} int main ( ) {
int N , Q ;
scanf ( "%d" , &N ) ;
for ( int i= ; i<=N- ; ++i ) {//读入建边
int _x , _y , _val ;
scanf ( "%d%d%d" , &_x , &_y , &_val ) ;
Add_Edge ( _x , _y , _val ) ;
Add_Edge ( _y , _x , _val ) ;
}
dep[ ] = ;//
DFS ( ) ;//以1为根节点DFS
scanf ( "%d" , &Q ) ;
while ( Q-- ) {
int _x , _y ;
scanf ( "%d%d" , &_x , &_y ) ;
printf ( "%d\n" ,LCA ( _x , _y ) ) ;
}
}

倍增

—————————————分割线—————————————

方法二:

    树的路径剖分算法求LCA。

    (不懂树链剖分点这,树链剖分——精讲)

    

    对于一棵树,我们将其剖分为若干条链,记录每个节点所在链的链头,该节点的父节点。

    当查询两节点LCA时

    伪代码:

    while ( x 与 y 不在同一条链上 )

        if ( x的DFS序 > y的DFS序 )x = pre [ start[ x ] ] //通过链头跳到另一条链上

        else if ( x的DFS序 < y的DFS序 )y = pre [ start[ y ] ]

    if ( x的DFS序 > y的DFS序 ) print  ( y )

    if ( x的DFS序 < y的DFS序 ) print  ( x )

算法图示:

动态图:

    至此,两个节点在同一条链上,算法结束,DFS序较小的节点 1 即为LCA 。

 算法模板&注释:

 void Add_Edge ( const int _x , const int _y , const int _val ) {
e[ ++cnt ].to = _y ;
e[ cnt ].val = _val ;
e[ cnt ].next = head[ _x ] ;
head[ _x ] = cnt ;
} int Init_DFS ( const int x , const int father ) {
int cnt_ , max_ = ;
for ( int i=head[ x ] ; i ; i=e[ i ].next ) {
int temp = e[ i ].to ;
if ( temp==father ) continue ;
Dis[ temp ] = Dis[ x ] + e[ i ] .val ;
int _ = Init_DFS ( temp , x ) ;
if ( _ > max_ ) {max_ = _ ; hv[ x ] = temp ;}
cnt_ +=_;
}
return cnt_ ;
} void DFS ( const int x , const int father ) {
if ( !start[ x ] ) start[ x ] = start[ father ] ;
DFN[ x ] = ++dfs_num ;
if ( hv[ x ] ) DFS ( hv[ x ] , x ) ;
for ( int i=head[ x ] ; i ; i =e[ i ].next ) {
if ( e[ i ].to != hv[ x ] && e[i].to != father ) {
int temp = e[ i ].to ;
start[ temp ] = temp ;
pre [ temp ] = x ;
DFS ( temp , x ) ;
}
}
} int LCA ( const int x , const int y ) {//start[]数组表示该点所在链的链头
int px = x , py = y ;
while ( start[ px ] != start[ py ] ) {//不在一条链上
if ( DFN[start[px]]>DFN[start[py] ] ) {//DFS序较大的跳
px = pre[ start[px] ] ;
}
else {
py = pre[ start[py] ] ;
}
}
return DFN[ px ] > DFN[ py ] ? py : px ;//返回DFS序较小的节点即为LCA
}

树链剖分

———————————————分割线———————————————

方法四:

    Tarjan算法求LCA.

    [ATTENTION]:LCA的Tarjan算法与强连通分量的Tarjan算法无关

2016-10-06 23:01:23

(完)

浅谈 LCA的更多相关文章

  1. 浅谈LCA

    目录 什么是LCA 倍增求LCA dfs bfs 树剖求LCA 什么是LCA LCA就是最近公共祖先 对于有根树\(Tree\)的两个结点\(u.v\),最近公共祖先\(LCA(T,u,v)\)表示一 ...

  2. 莫队浅谈&题目讲解

    莫队浅谈&题目讲解 一.莫队的思想以及莫队的前置知识 莫队是一种离线的算法,他的实现借用了分块的思想.在学习莫队之前,本人建议学习一下分块,并对其有一定的理解. 二.莫队 现给出一道例题:bz ...

  3. 浅谈 Fragment 生命周期

    版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Fragment 文中如有纰漏,欢迎大家留言指出. Fragment 是在 Android 3.0 中 ...

  4. 浅谈 LayoutInflater

    浅谈 LayoutInflater 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/View 文中如有纰漏,欢迎大家留言指出. 在 Android 的 ...

  5. 浅谈Java的throw与throws

    转载:http://blog.csdn.net/luoweifu/article/details/10721543 我进行了一些加工,不是本人原创但比原博主要更完善~ 浅谈Java异常 以前虽然知道一 ...

  6. 浅谈SQL注入风险 - 一个Login拿下Server

    前两天,带着学生们学习了简单的ASP.NET MVC,通过ADO.NET方式连接数据库,实现增删改查. 可能有一部分学生提前预习过,在我写登录SQL的时候,他们鄙视我说:“老师你这SQL有注入,随便都 ...

  7. 浅谈WebService的版本兼容性设计

    在现在大型的项目或者软件开发中,一般都会有很多种终端, PC端比如Winform.WebForm,移动端,比如各种Native客户端(iOS, Android, WP),Html5等,我们要满足以上所 ...

  8. 浅谈angular2+ionic2

    浅谈angular2+ionic2   前言: 不要用angular的语法去写angular2,有人说二者就像Java和JavaScript的区别.   1. 项目所用:angular2+ionic2 ...

  9. iOS开发之浅谈MVVM的架构设计与团队协作

    今天写这篇博客是想达到抛砖引玉的作用,想与大家交流一下思想,相互学习,博文中有不足之处还望大家批评指正.本篇博客的内容沿袭以往博客的风格,也是以干货为主,偶尔扯扯咸蛋(哈哈~不好好工作又开始发表博客啦 ...

随机推荐

  1. Unreal Engine4 学习笔记1 状态机 动画蓝图

    1.动画蓝图 包含 状态机 包含 混合空间BlendSpace,即状态机包含在动画蓝图的"动画图表中",而混合空间可用于在状态机中向某(没)一个状态输出最终POSE:    动画蓝 ...

  2. Spring容器初始化过程

    一.Spring 容器高层视图 Spring 启动时读取应用程序提供的Bean配置信息,并在Spring容器中生成一份相应的Bean配置注册表,然后根据这张注册表实例化Bean,装配号Bean之间的依 ...

  3. MATLAB中stem函数用法

    stem(Y) 将数据序列Y从x轴到数据值按照茎状形式画出,以圆圈终止.如果Y是一个矩阵,则将其每一列按照分隔方式画出. stem(X,Y)在X的指定点处画出数据序列Y.  stem(...,'fil ...

  4. Oracle自动备份脚本(网上找到的资料)

    废话不多说了,直接给大家贴代码了,具体代码如下所示: ? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 ...

  5. JavaWeb监听器详解

    1 JavaWeb监听器概述 在JavaWeb被监听的事件源为:ServletContext.HttpSession.ServletRequest,即三大域对象.有监听域对象"创建" ...

  6. 【HTML】HTML特殊符号【转http://www.cnblogs.com/web-d/archive/2010/04/16/1713298.html】

    HTML特殊字符编码大全:往网页中输入特殊字符,需在html代码中加入以&开头的字母组合或以&#开头的数字.下面就是以字母或数字表示的特殊符号大全.                   ...

  7. 【spring】 <tx:annotation-driven /> 的理解 【转载的】

    在使用SpringMvc的时候,配置文件中我们经常看到 annotation-driven 这样的注解,其含义就是支持注解,一般根据前缀 tx.mvc 等也能很直白的理解出来分别的作用.<tx: ...

  8. Liferay 6.2 改造系列之二十一:修改WebSphare下JSONWS服务不生效的BUG

    问题原因是WebSphare下,servletContext.getContextPath()获取到的值为“/”而非空字符串. 在/portal-master/portal-impl/src/com/ ...

  9. Liferay 6.2 改造系列之六:修改系统初始化信息

    将初始化过程修改为:中文语言 在/portal-master/portal-impl/src/system.properties文件中,有如下配置: # # Set the default local ...

  10. CSS3详解:background

    CSS3对于background做了一些修改,最明显的一个就是采用设置多背景,不但添加了4个新属性,并且还对目前的属性进行了调整增强. 1.多个背景图片 在css3里面,你可以再一个标签元素里应用多个 ...