洛谷 P3379 【模板】最近公共祖先(LCA)(离线)

题目链接

题意:给你一颗有n个点的树以及其根节点s,有m个询问,每次询问a,b的最近公共祖先。

思路:LCA模板题。

LCA(最近公共祖先)(离线):离线存下所有询问,然后我们可以选择dfs遍历一遍整棵树,在遍历的过程中我们设一个数组vis,还未找到的节点vis=0,找到的节点vis=1,当其所有孩子均遍历过后,vis=2。

那么据此,当我们访问到b的时候,我们可以把vis[a]分为三种情况:

  1. vis[a]=1,则b位于一颗a为根的子树上,所以LCA(a,b)=a。
  2. vis[a]=2,首先,设一个father数组,初始化father[i]=i,那么当把vis[a]置为2时,说明a的孩子已经遍历完,则设father[a]=与a最接近的vis=1的点(此通过并查集在询问的时候进行维护),而且当前正在遍历father[a]的孩子,显然,a,b不属于以father[a]为根的树的同一颗子树上,所以,LCA(a,b)=father[a]。
  3. vis[a]=0,则不用理会,当访问到a时,b会出现1或2情况。

优化:vis[a]=0的情况没有意义但程序运行的时候仍会出现。所以可以先dfs一遍预处理出树上节点的遍历顺序对于每个询问,只要在找到顺序靠后的点的时候进行判断即可。(这题不优化更快- -。。。)

该题会卡vector,故要用邻接链表存储。。

#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<string>
#include<iostream>
#include<vector>
#define inf 0x3f3f3f3f
#define p 1000000007
#define NUM 510000
using namespace std;
int m,n,s;
struct node
{
    int vertex;
    int next;
}askedge[NUM];
int aske[NUM]={};
struct link
{
    int to;
    int next;
}edge[2*NUM];
int e[NUM]={};
int ans[NUM];
int father[NUM],vis[NUM]={},tern[NUM];
int getfather(int x)
{
    if(father[x]==x)
        return x;
    return father[x]=getfather(father[x]);
}
int dfs(int v,int& t)
{
    int x;
    tern[v]=t;
    vis[v]=1;
    for(int i=e[v];i!=0;i=edge[i].next)
    {
        x=edge[i].to;
        ++t;
        if(vis[x]!=0)continue ;
        dfs(x,t);
    }
}
void LCA(int pre,int v)
{
    int x;
    node y;
    vis[v]=1;
    for(int i=aske[v];i!=0;i=askedge[i].next)
        ans[i]=getfather(askedge[i].vertex);
    for(int i=e[v];i!=0;i=edge[i].next)
    {
        x=edge[i].to;
        if(vis[x]!=0)continue ;
        LCA(v,x);
    }
    vis[v]=2;
    father[v]=getfather(pre);
}
int main()
{
    int x,y;
    scanf("%d%d%d",&n,&m,&s);
    for(int i=1;i<n;++i)
    {
        scanf("%d%d",&x,&y);
        edge[i<<1].to=x;
        edge[i<<1].next=e[y];
        e[y]=i<<1;
        edge[(i<<1)-1].to=y;
        edge[(i<<1)-1].next=e[x];
        e[x]=(i<<1)-1;
    }
    int step=1;
    dfs(s,step);
    memset(vis,0,sizeof(vis));
    for(int i=1;i<=n;++i)
        father[i]=i;
    for(int i=1;i<=m;++i)
    {
        scanf("%d%d",&x,&y);
        if(tern[y]<tern[x])
        {
            askedge[i].vertex=y;
            askedge[i].next=aske[x];
            aske[x]=i;
        }
        else
        {
            askedge[i].vertex=x;
            askedge[i].next=aske[y];
            aske[y]=i;
        }
    }
    LCA(s,s);
    for(int i=1;i<m;++i)
        printf("%d\n",ans[i]);
    printf("%d",ans[m]);
}


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值