百度百科关于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。

                关于LCA的三种解法

        关于LCA的三种解法

 

             关于LCA的三种解法

看完这几张图你可能认为这个认为这个算法太简单了(比如说当初不管数据范围的我)只要疯狂向上一格一格跳就好了,dfs一遍就能跑出来了。但是一个一个跳这不会T嘛?因此出现了倍增!!!!什么是倍增?表面理解就是按照倍数增大,计算机的基础是什么?01!!!就是二进制!我们可以用二进制来表示所有的数。对于每一个节点我们只要知道其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));
    }
}
View Code

相关文章: