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

· · 题解

刚学了st写法,写一篇题解练练手

首先,我们按照dfs的过程得到一个欧拉序(访问到一个数就加到序列末尾 (包括回溯)

结合图理解一下欧拉序

然后我们观察上图,感性理解一下就能发现lca是fir[x],fir[y]中dep最小的(fir为第一次出现的位置)

所以我们就得到了一个rmq问题:

在fir[x]~fir[y]中,dep最小的是哪个

显然,我们可以用st表维护,然后查询就可以做到O(1)了

感性理解,你一个数,最多在被访问到时tim++,回溯时,父亲tim++,所以st表开两倍

#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+25;
int n,m,s,st[N*2][20],dfn[N],fir[N],tim,dep[N],fa[N];
struct edge
{
    int next,to;
}p[2*N];
int head[N],num;
void ad(int x,int y)
{
    p[++num]=edge{head[x],y};
    head[x]=num;
}
void dfs(int u)
{
    dfn[++tim]=u; //欧拉序中第i个数是啥 
    if(!fir[u]) fir[u]=tim; //u在欧拉序中第一次出现的位置 
    st[tim][0]=u; //st表 
    for(int i=head[u];i;i=p[i].next)
    {
        int v=p[i].to;
        if(v==fa[u]) continue;
        fa[v]=u;
        dep[v]=dep[u]+1;
        dfs(v);
        dfn[++tim]=u;
        st[tim][0]=u;
    }
}
int lca(int x,int y)
{
    int l=fir[x],r=fir[y];
    if(r<l) swap(l,r);
    int lg=(log2(r-l+1));
//  printf("@%d %d %d\n",l,r,lg);
//  printf("@%d %d %d %d %d\n",l,r,lg,r-l+1,log2(r-l+1));
    return dep[st[l][lg]]>dep[st[r-(1<<lg)+1][lg]]?st[r-(1<<lg)+1][lg]:st[l][lg];
}
int main() 
{
    cin>>n>>m>>s;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        ad(x,y);
        ad(y,x);
    }
    dfs(s);
//  for(int i=1;i<=tim;i++) st[i][0]=dfn[i];
//  for(int i=1;i<=n;i++) printf("#%d %d %d\n",i,dfn[i],fir[i]);
    for(int j=1;j<=20;j++)
        for(int i=1;i+(1<<j)-1<=tim;i++)
            if(dep[st[i][j-1]]>dep[st[i+(1<<(j-1))][j-1]]) st[i][j]=st[i+(1<<(j-1))][j-1];
            else st[i][j]=st[i][j-1];
//  for(int i=1;i<=n;i++) for(int j=0;j<=2;j++) printf("%d %d %d\n",i,j,st[i][j]);
    while(m--)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        printf("%d\n",lca(x,y));
    }
    return 0;
}