百度百科关于LCA的解释:LCA(Least Common Ancestors),即最近公共祖先,是指在有根树中,找出某两个结点u和v最近的公共祖先。(有多种变型例如求两点间的距离如HDU2586,求最大公共的长度如CodeForces - 832D 等等)
题目: POJ 1984 HDU 2586 ZOJ 3195 POJ 1330 CodeForces - 832D
1.跳跃法/倍增LCA优化(在线算法)
倍增练习题:CodeForces - 932D (非LCA)
假设我们求两节点的LCA,需要进行以下几种操作:
1.优先处理出各个节点的深度;
2.判断两节点的深度是否相同;
3.如果不相同,对深度大的节点进行跳跃操作,直到两点深度相同;
4.判断当前两节点所在的节点是否为同一节点,是则其为LCA,否则继续操作5;
5.判断两个节点是否具有相同父亲节点,是则父亲节点为LCA,否则继续操作6;
6.两个节点同时跳相同长度(但是两个节点不能跳到同一个节点去)回至操作5.
假设我们求5和6的LCA,那么我们需要先将5从depth:3开始起跳,一步一步向上跳,直到从5跳到2(即与6相同深度)因为2和6有同一个父亲节点所以1就是5和6的LCA。
看完这几张图你可能认为这个认为这个算法太简单了(比如说当初不管数据范围的我)只要疯狂向上一格一格跳就好了,dfs一遍就能跑出来了。但是一个一个跳这不会T嘛?因此出现了倍增!!!!什么是倍增?表面理解就是按照倍数增大,计算机的基础是什么?0和1!!!就是二进制!我们可以用二进制来表示所有的数。对于每一个节点我们只要知道其2^j层的祖先是谁,就能让任意一个节点从自身以logn的速度快速跳跃到1.另赋超生动形象的倍增讲解:http://blog.csdn.net/jarjingx/article/details/8180560
核心代码:
//father[][]第一维是表示节点,第二维表示节点的第i个祖先。 //n是节点个数,Logn是根号n(取整) for(int i=1;i<=Logn;i++) father[u][i]=father[father[u][i-1]][i-1];
练手模版题传送门:POJ 1330
详细代码(模版含解释):
关于下面这篇代码处理入度的问题:题目中给出了父亲与儿子的关系所以要进行入度处理,不能随意dfs
#include <cstdio> const int N=1e4+5; using namespace std; int fa[N][14]; int dep[N]; int head[N]; int nx[N]; int to[N]; int tot=1; bool vis[N]; int in[N]; int L,R; void add(int u,int v){//链式前向星存图 to[tot]=v; nx[tot]=head[u]; head[u]=tot++; } void init(int n){//初始化 for(int i=0;i<=n;i++)fa[i][0]=0,in[i]=0,vis[i]=0,head[i]=0,dep[i]=0; tot=1; } void dfs(int u,int d){//dfs处理深度,处理2^j的祖先关系 vis[u]=1; dep[u]=d; for(int i=1;i<14;i++)fa[u][i]=fa[fa[u][i-1]][i-1]; for(int i=head[u];i;i=nx[i]){ int v=to[i]; if(!vis[v])dfs(v,d+1); } } int LCA(int u,int v){ if(dep[u]<dep[v])swap(u,v);//保证u是深度大的那个节点 int d=dep[u]-dep[v];//深度之差 for(int i=0;(1<<i)<=d;i++){//(1<<i)<=d是为了让u在保证不会跳过v的情况下进行跳跃 if((1<<i)&d){ //(1<<i)&d其实就是转化二进制问那几个点可以跳跃 u=fa[u][i];//例如差为5(101)只要在i==0和i==2的情况下跳跃(如果还是不懂就模拟一下) } } if(u==v)return u;//如果两个节点直接相等那么就如操作4所说,该点就是LCA,否则继续进行相同长度的跳跃 for(int i=13;i>=0;i--){ if(fa[v][i]!=fa[u][i]){ u=fa[u][i]; v=fa[v][i]; } } return fa[u][0]; } int main(){ int t; scanf("%d",&t); while(t--){ tot=1; int n; scanf("%d",&n); init(n); for(int i=1;i<n;i++){ int l,r; scanf("%d %d",&l,&r); add(l,r); fa[r][0]=l; in[r]++;//处理入度 } scanf("%d%d",&L,&R); for(int i=1;i<=n;i++) if(!in[i]){ dfs(i,0);//必须从入度为0的点开始dfs break; } printf("%d\n",LCA(L,R)); } }