之前学习过一次lca,是用暴力,倍增,tarjan离线处理的。
之前就知道lca还有其他两种写法,不过那个时候还不会树剖,就没学。
今天学习树剖之后,发现lca树剖写法不就是个裸的树剖吗。
预处理 O(n) 查询logn 比倍增还优秀一点。
今天就补上没学的那两种lca的写法了。

P3379 【模板】最近公共祖先(LCA):

题目描述
如题,给定一棵有根多叉树,请求出指定两个点直接最近的公共祖先。

输入格式
第一行包含三个正整数N、M、S,分别表示树的结点个数、询问的个数和树根结点的序号。

接下来N-1行每行包含两个正整数x、y,表示x结点和y结点之间有一条直接连接的边(数据保证可以构成树)。

接下来M行每行包含两个正整数a、b,表示询问a结点和b结点的最近公共祖先。

输出格式
输出包含M行,每行包含一个正整数,依次为每一个询问的结果。

输入输出样例
输入 #1 复制
5 5 4
3 1
2 4
5 1
1 4
2 4
3 2
3 5
1 2
4 5
输出 #1 复制
4
4
1
4
4
说明/提示
时空限制:1000ms,128M

数据规模:

对于30%的数据:N<=10,M<=10

对于70%的数据:N<=10000,M<=10000

对于100%的数据:N<=500000,M<=500000

一、树剖:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int maxn=500500;
int head[maxn],ver[maxn<<1],nt[maxn<<1];
int d[maxn],f[maxn],son[maxn],si[maxn];
int rk[maxn],id[maxn],top[maxn];
int tot=1,cnt=0;

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}

void dfs1(int x,int fa)
{
    int max_son=0;
    si[x]=1;
    for(int i=head[x];i;i=nt[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        d[y]=d[x]+1;
        f[y]=x;
        dfs1(y,x);
        si[x]+=si[y];
        if(si[y]>max_son) max_son=si[y],son[x]=y;
    }
}

void dfs2(int x,int t)
{
    top[x]=t;
    id[x]=++cnt;
    rk[cnt]=x;
    if(!son[x]) return ;
    dfs2(son[x],t);
    for(int i=head[x];i;i=nt[i])
    {
        int y=ver[i];
        if(y!=son[x]&&y!=f[x])
            dfs2(y,y);
    }
}

int lca(int x,int y)
{
    while(top[x]!=top[y])
    {
        if(d[top[x]]<d[top[y]]) swap(x,y);
        x=f[top[x]];
    }
    return id[x]>id[y]?y:x;
}

int main(void)
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    int x,y;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs1(s,0);
    dfs2(s,s);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}

二、dfs+st算法:
nlogn预处理,O(1)查询。
log函数的效率较高,可以即用即调用,不过可以O(n)处理出来所有的log2(len)

前置知识,RMQ(区间最值)的ST算法。
P3865 【模板】ST表:
题目背景
这是一道ST表经典题——静态区间最大值

请注意最大数据时限只有0.8s,数据强度不低,请务必保证你的每次查询复杂度为 O(1)

题目描述
给定一个长度为 N 的数列,和 M 次询问,求出每一次询问的区间内数字的最大值。

输入格式
第一行包含两个整数 N, M 分别表示数列的长度和询问的个数。

第二行包含 N 个整数依次表示数列的第 i 项。

接下来 M行,每行包含两个整数 l, r,表示查询的区间为 [ l, r]
输出格式
输出包含 M行,每行一个整数,依次表示每一次询问的结果。

输入输出样例
输入 #1 复制
8 8
9 3 1 7 5 6 0 8
1 6
1 5
2 7
2 6
1 8
4 8
3 7
1 8
输出 #1 复制
9
9
7
7
9
8
7
9
n<=1e5,m<=1e6

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int maxn=101000;
int f[maxn][20],a[maxn], _log[maxn];
int n,m;
void ST(void)
{
    for(int i=1;i<=n;i++)
        f[i][0]=a[i],_log[i]=log(i)/log(2);
    int t=_log[n]+1;
    for(int j=1;j<=t;j++)
    {
        for(int i=1;i<=n-(1<<j)+1;i++)
            f[i][j]=max(f[i][j-1],f[i+(1<<(j-1))][j-1]);
    }
}

int get_max(int l,int r)
{
    int k=_log[r-l+1];
    return max(f[l][k],f[r-(1<<k)+1][k]);
}

int main(void)
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)
        scanf("%d",&a[i]);
    ST();
    int x,y;
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",get_max(x,y));
    }
    return 0;
}

②dfs+st
dfs这棵树,得到一个序列。
这个序列是,每个节点第一次被访问时做标记,每个节点每访问完一个儿子时做标记。
经证明,这样的dfs序的长度为2*n-1。(猜测证明法)
处理出每个节点在dfs树上的深度,第一次访问这个节点时dfs的标号。
对于节点x,y,id(x)与id(y)之间dfs的标号深度最小的就是它们的lca。
以上洛谷的求lca的题目为例,代码如下。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#include<set>
#include<vector>
#define ll long long
#define llu unsigned ll
using namespace std;
const int maxn=501000;
int head[maxn],ver[maxn<<1],nt[maxn<<1];
int d[maxn],id[maxn],rk[maxn<<1],dd[maxn<<1];
int f[maxn<<1][21],_log[maxn<<1];
int tot=1,cnt=0;

void add(int x,int y)
{
    ver[++tot]=y,nt[tot]=head[x],head[x]=tot;
}

void dfs(int x,int fa)
{
    id[x]=++cnt;rk[cnt]=x;dd[cnt]=d[x];
    for(int i=head[x];i;i=nt[i])
    {
        int y=ver[i];
        if(y==fa) continue;
        d[y]=d[x]+1;
        dfs(y,x);
        rk[++cnt]=x,dd[cnt]=d[x];
    }
}

void ST(void)
{
    for(int i=1;i<=cnt;i++)
        f[i][0]=i,_log[i]=log(i)/log(2);
    int t=_log[cnt]+1;
    for(int j=1;j<=t;j++)
    {
        for(int i=1;i<=cnt-(1<<j)+1;i++)
            f[i][j]=dd[f[i][j-1]]<dd[f[i+(1<<(j-1))][j-1]]?f[i][j-1]:f[i+(1<<(j-1))][j-1];

    }
}

int lca(int x,int y)
{
    if(id[x]>id[y]) swap(x,y);
    x=id[x],y=id[y];
    int k=_log[y-x+1];
    return dd[f[x][k]]<dd[f[y-(1<<k)+1][k]]?rk[f[x][k]]:rk[f[y-(1<<k)+1][k]];
}

int main(void)
{
    int n,m,s;
    scanf("%d%d%d",&n,&m,&s);
    int x,y;
    for(int i=1;i<n;i++)
    {
        scanf("%d%d",&x,&y);
        add(x,y);
        add(y,x);
    }
    dfs(s,0);
    ST();
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}