LCA在线算法
2016-08-18 16:35
176 查看
LCA(Least Common Ancestors),即最近公共祖先。对于有根树T的两个结点u、v,最近公共祖先LCA(T,u,v)表示一个结点x,满足x是u、v的祖先且x的深度尽可能大。
求LCA的算法有很多种,分为在线和离线。离线算法一般有tarjan,在线算法则是树上倍增与rmq。
这里主要讲下在线算法吧:-)
经过“肉眼扫描算法”,我们可以很快的得出4和6的最近公共祖先是1。
倍增
对于两个同一层(也就是深度一样)的结点,向上寻找他们的最近公共祖先。对于上图的4和6来说,我们首先判断 4 的父亲 6 的父亲是不是同一个结点,如果不是,则继续向上重复 “判断父亲是不是相同的” 这个操作。直到找到为止。
显然,这么一层层的找是很愚蠢的。:-(
看起来比较聪明的办法是,首先预处理出,每个点向上跳2^j次,会跳在哪里。根据我们的生活常识,我们可以通过这些2^(i、j、k…)组合出任意一个正整数,也就是说我们可以通过每次跳2^j次跳出我们想要的。
然后我们还要求出跳2^j次所到达的点——
预处理就这样完了:-)
再回到这张图。首先我们讨论的同一层的结点,比如说4和6,我们将他们一起向上跳2^20(这个数字可以按照题目数据规模更改)次,结果发现跳出去了…于是我们跳2^19次,2^18次……直到我们第一次跳到了这样的一层——对于所查询的x,y,在这一层的祖宗x1,y1不是同一个。
可以用脚趾头想到,x1与y1的最近公共祖先,就是x,y的最近公共祖先。然后我们就从x1,y1继续向上跳, 可以用脚趾头想到,由于我们是第一次跳到这样的一层,x与x1的深度差异,一定比x1与x1,y1的最近公共祖先的深度差异小。
假设我们是跳了2^j次跳到了x1,我们只要以x1为起点,从2^(j-1)次开始跳。可以用手趾头想到,这个时候我们跳到的两个点,xn,yn的父亲,就是x,y的最近公共祖先。
是的就这样。^_^
由此我们很容易的就解决了,当x,y深度一样时,查询x与y的最近公共祖先问题。但是更多的时候,x,y是不在同一层的。此时,我们可以运用上面的思想,将深度较大的点向上跳,使得两个点处于同一深度,转换成上面的问题。
查询代码如下:
如果有错误,错误是我的:-(
RMQ
由于我不知道怎么长篇大论描述这个算法的神奇,所以我们从左至右来dfs这棵树吧。
经过dfs,我们可以得到这样的一些数组:
对于上面这棵树,这些数组是这样的(都忽略[0]):
对于任意两个点x,y中,假设first[x] < first[y],则必有first[x] <= ( ver[ ? ]= LCA(x,y) ) <= first[y]。为了更好地理解可以用纸自己画一画。
知道这一点后,求最近公共祖先的问题转化为,求?点满足ver[z]=? , first[x]<= ( ver[z] ) <=first[y],且depth[z]最小。
显然,我们可以用RMQ解决这个问题。:-)
代码如下:
事实上,在实际的操作中,我们并不需要ver数组,first数组就已经能够很好地满足我们的要求了。
查询代码如下:
如果有错误,请让我吃掉它:-(
最后附上几道水题:
求树上两点之间距离 poj 1986 Distance Queries 点我点我:-)
求树上两点之间距离加强版 hdu 2874 Connections between cities 点我点我:-)
求LCA的算法有很多种,分为在线和离线。离线算法一般有tarjan,在线算法则是树上倍增与rmq。
这里主要讲下在线算法吧:-)
经过“肉眼扫描算法”,我们可以很快的得出4和6的最近公共祖先是1。
倍增
对于两个同一层(也就是深度一样)的结点,向上寻找他们的最近公共祖先。对于上图的4和6来说,我们首先判断 4 的父亲 6 的父亲是不是同一个结点,如果不是,则继续向上重复 “判断父亲是不是相同的” 这个操作。直到找到为止。
显然,这么一层层的找是很愚蠢的。:-(
看起来比较聪明的办法是,首先预处理出,每个点向上跳2^j次,会跳在哪里。根据我们的生活常识,我们可以通过这些2^(i、j、k…)组合出任意一个正整数,也就是说我们可以通过每次跳2^j次跳出我们想要的。
//首先dfs预处理出点的深度,以及向上跳2^0步所到达的点(就是这个点的父亲)。 void dfs(int node) { for(int i=head[node];i;i=nxt[i]) { if(depth[to[i]])continue; fat2[to[i]][0]=node; depth[to[i]]=depth[node]+1; dfs(to[i]); } }
然后我们还要求出跳2^j次所到达的点——
//倍增求出跳2^j(j>=1 && ( 1 <<j) <=n )次所到达的点 for(int j=1;(1<<j)<=n;j++) for(int i=1;i<=n;i++) if(fat[i][j-1]!=-1) fat[i][j]=fat[fat2[i][j-1]][j-1]; //fat[i][j]表示从i结点开始,向上跳2^j步,所到达的结点编号
预处理就这样完了:-)
再回到这张图。首先我们讨论的同一层的结点,比如说4和6,我们将他们一起向上跳2^20(这个数字可以按照题目数据规模更改)次,结果发现跳出去了…于是我们跳2^19次,2^18次……直到我们第一次跳到了这样的一层——对于所查询的x,y,在这一层的祖宗x1,y1不是同一个。
可以用脚趾头想到,x1与y1的最近公共祖先,就是x,y的最近公共祖先。然后我们就从x1,y1继续向上跳, 可以用脚趾头想到,由于我们是第一次跳到这样的一层,x与x1的深度差异,一定比x1与x1,y1的最近公共祖先的深度差异小。
假设我们是跳了2^j次跳到了x1,我们只要以x1为起点,从2^(j-1)次开始跳。可以用手趾头想到,这个时候我们跳到的两个点,xn,yn的父亲,就是x,y的最近公共祖先。
是的就这样。^_^
由此我们很容易的就解决了,当x,y深度一样时,查询x与y的最近公共祖先问题。但是更多的时候,x,y是不在同一层的。此时,我们可以运用上面的思想,将深度较大的点向上跳,使得两个点处于同一深度,转换成上面的问题。
查询代码如下:
int find_lca(int x,int y) { if(depth[x]<depth[y])swap(x,y); for(int j=20;j>=0;j--) if(depth[x]>=depth[y]+(1<<j)) x=fat[x][j]; if(x==y)return x; for(int j=20;j>=0;j--) if(fat[x][j]!=-1 && fat[x][j]!=fat[y][j]) { x=fat[x][j]; y=fat[y][j]; } return fat[x][0]; }
如果有错误,错误是我的:-(
RMQ
由于我不知道怎么长篇大论描述这个算法的神奇,所以我们从左至右来dfs这棵树吧。
经过dfs,我们可以得到这样的一些数组:
first[i]表示第一次访问i结点的次序 ver[i]表示第i次访问了哪个结点 depth[i]表示ver[i]所表示点的深度
对于上面这棵树,这些数组是这样的(都忽略[0]):
first[]=1,2,14,3,5,15,6,10,7 ver[]=1,2,4,2,5,7,9,7,5,8,5,2,1,3,6,3,1 depth[]=1,2,3,2,3,4,5,4,3,4,3,2,1,2,3,2,1
对于任意两个点x,y中,假设first[x] < first[y],则必有first[x] <= ( ver[ ? ]= LCA(x,y) ) <= first[y]。为了更好地理解可以用纸自己画一画。
知道这一点后,求最近公共祖先的问题转化为,求?点满足ver[z]=? , first[x]<= ( ver[z] ) <=first[y],且depth[z]最小。
显然,我们可以用RMQ解决这个问题。:-)
代码如下:
void dfs(int node,int dep) { ver[++tot]=node; first[node]=tot; depth[tot]=dep; vis[node]=1; for(int i=head[node];i!=-1;i=nxt[i]) { if(vis[to[i]])continue; dfs(to[i],dep+1); ver[++tot]=node; depth[tot]=dep; } } void init(int nn)//nn为访问次数 { for(int i=1;i<=nn;i++)dp[i][0]=i; memset(vis,0,sizeof(vis)); dfs(1,1); for(int j=1;(1<<j)<=nn;j++) for(int i=1;i+(1<<j)-1<=nn;i++) { int a = dp[i][j-1],b=dp[i+(1<<(j-1))][j-1]; if(depth[a]<depth[b])dp[i][j]=a; else dp[i][j]=b; } }
事实上,在实际的操作中,我们并不需要ver数组,first数组就已经能够很好地满足我们的要求了。
查询代码如下:
int askrmq(int left,int right) { int i=0; while((1<<(i+1))<=right-left+1)i++; int a=dp[left][i],b=dp[right-(1<<i)+1][i]; if(depth[a]<depth[b])return a; return b; } int find_lca(< 9c1b span class="hljs-keyword">int x,int y) { x=first[x];y=first[y]; if(x>y)swap(x,y); return ver[askrmq(x,y)]; }
如果有错误,请让我吃掉它:-(
最后附上几道水题:
求树上两点之间距离 poj 1986 Distance Queries 点我点我:-)
求树上两点之间距离加强版 hdu 2874 Connections between cities 点我点我:-)
相关文章推荐
- POJ2763-LCA在线算法+树状数组
- LCA在线算法(RMQ st算法的结合)
- 图论 LCA在线算法 倍增法
- POJ 1330 Nearest Common Ancestors(LCA在线算法)
- poj1330 LCA在线算法 复杂度O(nlogn)
- HDU 2586 最近公共祖先 LCA在线算法
- LCA在线算法(hdu2586)
- HDU2586【LCA在线算法】
- LCA在线算法(hdu2586)
- HDU 2874 LCA在线算法RMQ
- LCA在线算法
- LCA在线算法详解
- TOJ 1607 Distance Queries -- LCA在线算法 RMQ
- HDU 2586 How far away ?(LCA在线算法实现)
- hdu4547——CD操作 LCA在线算法
- RMQ和LCA在线算法
- LCA在线算法ST算法
- hdu 2586 LCA在线算法
- hihocoder1069最近公共祖先·三(LCA在线算法--DFS+RMQ-ST)
- LCA在线算法