题意:给你一颗有n个点的树以及其根节点s,有m个询问,每次询问a,b的最近公共祖先。
思路:LCA模板题。
LCA(最近公共祖先)(离线):离线存下所有询问,然后我们可以选择dfs遍历一遍整棵树,在遍历的过程中我们设一个数组vis,还未找到的节点vis=0,找到的节点vis=1,当其所有孩子均遍历过后,vis=2。
那么据此,当我们访问到b的时候,我们可以把vis[a]分为三种情况:
- vis[a]=1,则b位于一颗a为根的子树上,所以LCA(a,b)=a。
- 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]。
- 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]);
}