三种方法:

1.树链剖分(在上一篇代码中已经讲解得很详细,不再一一赘述)

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<climits>
#include<ctime>
#include<queue>
#include<vector>
#include<map>
#include<algorithm>
#include<iomanip>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
typedef long long LL;
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
const int M=500001;
int n,m,root,cnt=0,head[M],to[M<<1],next[M<<1],son[M],siz[M],top[M],dep[M],fa[M];
void Insert(int u,int v){
    to[cnt]=v;
    next[cnt]=head[u];
    head[u]=cnt++;
    to[cnt]=u;
    next[cnt]=head[v];
    head[v]=cnt++;
}
void dfs1(int u){
    siz[u]=1;
    for(int i=head[u];i!=-1;i=next[i]){
        int v=to[i];
        if(v!=fa[u]){
            fa[v]=u;
            dep[v]=dep[u]+1;
            dfs1(v);
            siz[u]+=siz[v];
            if(!son[u]||siz[son[u]]<siz[v])son[u]=v;
        }
    }
}
void dfs2(int u){
    if(son[u]){
        top[son[u]]=top[u];
        dfs2(son[u]);
    }
    for(int i=head[u];i!=-1;i=next[i]){
        int v=to[i];
        if(v!=fa[u]&&v!=son[u]){
            top[v]=v;
            dfs2(v);
        }
    }
}
int lca(int x,int y){
    while(1){
        int tx=top[x],ty=top[y];
        if(tx==ty)return (dep[x]<dep[y]?x:y);
        if(dep[tx]<dep[ty])y=fa[ty];else x=fa[tx];
    }
}
int main(){
    n=read();m=read();root=read();
    memset(head,-1,sizeof(head));
    rep(i,1,n-1){
        int x=read(),y=read();
        Insert(x,y);
    }
    dep[root]=1;
    dfs1(root);
    top[root]=root;
    dfs2(root);
    while(m--){
        int x=read(),y=read();
        printf("%d\n",lca(x,y));
    }
    return 0;
}

2.Tarjan(慎用!如果题目是按照树剖卡常数的话,则此算法会MLE(空间大小为树剖的两倍))

有个特别形象的讲解在这里:http://www.cnblogs.com/JVxie/p/4854719.html

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<vector>
#include<map>
#include<algorithm>
#include<climits>
#include<ctime>
#include<iomanip>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}

const int M=500001;
int n,m,root;
bool vis[M]={0};
int cnt=0,tot=0,f[M],ans[M],to[M<<1],head[M],next[M],q[M<<1],nt[M<<1],h[M];
void insert_edge(int x,int y){
    to[cnt]=y;
    next[cnt]=head[x];
    head[x]=cnt++;
    to[cnt]=x;
    next[cnt]=head[y];
    head[y]=cnt++;
}
void insert_query(int x,int y){
    q[tot]=y;
    nt[tot]=h[x];
    h[x]=tot++;
    q[tot]=x;
    nt[tot]=h[y];
    h[y]=tot++;
}
int Find(int x){
    if(f[x]==x)return x;
    else return f[x]=Find(f[x]);
}
void dfs(int fa,int u){
    for(int i=head[u];i!=-1;i=next[i]){
        int v=to[i];
        if(v!=fa)dfs(u,v);
    }
    for(int i=h[u];i!=-1;i=nt[i]){
        int v=q[i];
        if(vis[v])ans[i/2+1]=Find(v);
    }
    vis[u]=1;
    f[u]=fa;
}
            
int main(){
    n=read();m=read();root=read();
    memset(head,-1,sizeof(head));
    memset(h,-1,sizeof(h));
    rep(i,1,n)f[i]=i;
    rep(i,1,n-1){
        int x=read(),y=read();
        insert_edge(x,y);
    }
    rep(i,1,m){
        int x=read(),y=read();
        insert_query(x,y);
    }
    dfs(root,root);
    rep(i,1,m)printf("%d\n",ans[i]);
    return 0;
}

 3.倍增

这个算法思想非常重要,首先保存每个节点向上走的第1、2、4、8...个祖先,复杂度是log级别的,然后找LCA的时候就可以快速查找了。

这个算法中用到了一些位运算的知识,需要用心体会。

#include<iostream>
#include<cstring>
#include<cstdio>
#include<queue>
#include<vector>
#include<map>
#include<algorithm>
#include<climits>
#include<ctime>
#include<iomanip>
#include<cmath>
using namespace std;
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define dep(i,a,b) for(int i=a;i>=b;i--)
inline int read(){
    int x=0;char ch=getchar();
    while(ch<'0'||ch>'9')ch=getchar();
    while(ch>='0'&&ch<='9'){
        x=x*10+ch-'0';
        ch=getchar();
    }
    return x;
}
const int M=500001;
int dp[M][19],to[M<<1],next[M<<1],head[M],dep[M];
int cnt=0,n,m,root;
void insert_edge(int x,int y){
    to[cnt]=y;
    next[cnt]=head[x];
    head[x]=cnt++;
    to[cnt]=x;
    next[cnt]=head[y];
    head[y]=cnt++;
}
void dfs(int u){
    for(int i=head[u];i!=-1;i=next[i]){
        int v=to[i];
        if(v!=dp[u][0]){
            dep[v]=dep[u]+1;//记录深度
            dp[v][0]=u;//保存某个节点的父亲节点,即第1个祖先
            dfs(v);
        }
    }
}
void init(){
    for(int j=1;(1<<j)<=n;j++)//预处理,超出根节点的部分不用管,计算LCA的时候不会超出根节点
    rep(i,1,n)dp[i][j]=dp[dp[i][j-1]][j-1];//一个节点的第2^i个祖先即为它的第2^(i-1)个祖先的第2^(i-1)个祖先
}
int lca(int x,int y){
    if(dep[x]>dep[y])swap(x,y);//保证y的深度较大
    int f=dep[y]-dep[x];//计算深度差
    for(int i=0;(1<<i)<=f;i++)//首先将x与y移动到同一深度上
        if((1<<i)&f)y=dp[y][i];//尤其注意这里的位运算,我们需要将y向上移动f位,首先将f表示成二进制,(1<<i)&f的值为1表示f从右往左第i位为1,则将y向上移动2^i位
    if(x==y)return x;//若x和y是同一节点,则lca已求出
    else{
        dep(i,(int)log2(n),0)if(dp[x][i]!=dp[y][i])x=dp[x][i],y=dp[y][i];//从最大跨度开始逆推,若dp[x][i]!=dp[y][i],说明将x和y同时向上移动2^i步后仍没有找到lca,则修改x和y,若已经找到了说明lca为该位置的某个后代节点,则不修改
        return dp[x][0];//注意循环的最后一次操作,如果dp[x][0]和dp[y][0]相同,没有进行修改,则x和y的父亲节点即为所求的lca
    }
}
int main(){
    n=read();m=read(),root=read();
    memset(head,-1,sizeof(head));
    dp[root][0]=root;
    rep(i,1,n-1){
        int x=read(),y=read();
        insert_edge(x,y);
    }
    dep[root]=1;
    dfs(root);//一次dfs初始化父亲节点
    init();
    while(m--){
        int x=read(),y=read();
        printf("%d\n",lca(x,y));
    }
    return 0;
}