Tarjan求LCA
2020-03-14
发布于摸鱼酱的个人博客
首先,Tarjan算法是一个离线求LCA的算法,与倍增不同。
倍增的做法是\(O(n)\)预处理,\(O(log_2n)\)回答每一次询问(倍增),也就是说可以每输入一次询问就立刻计算输出,是在线算法。
Tarjan呢,则是先读入完所有的数据,再用一次\(dfs\)遍历之后全部输出,在读入完之前不能计算出答案,是离线算法。
接下来就是具体的过程。
先假设有这样一棵树。
对于这棵树,我们再假装有这样几组询问:
\(LCA(4,6)\)
\(LCA(2,10)\)
\(LCA(1,6)\)
首先我们将这几组询问分别存储到对应的点上。假如第\(i\)组询问需要求\(LCA(u,v)\),为了让从u,v都能够访问到这组询问,我们就以每一个点编号为下标,开一个存放{目标点,询问编号}的vector,就像这样.
struct node//定义部分
{
int v,id;
};
vector<node> ask[500010];
...
//对于每组询问u,v
ask[u].push_back((node){v,i});
ask[v].push_back((node){u,i});
...
为了更清晰,在示例图中我们就不记录询问编号。记录完成后如下图:
这棵询问树完成之后,输入也就完成了,接下来就是离线求LCA的环节了。
在这个环节,我们采用dfs对其进行一次遍历,但是过程中会穿插来自并查集的get求祖先操作。初始化即每个点都是自己的父结点。
贴一下get操作,后面比较好理解。
int get(int x)
{
return fa[x]==x?x:fa[x]=get(fa[x]);
/*或者写成下面的形式
if(fa[x]==x)return x;
else return fa[x]=get(fa[x]);
*/
}
我们把目前经历过但是还没回溯的点标记为红色R(无实际意义,为了看得更清晰),回溯了的点标记为蓝色B。是否遍历过就用一个bool数组来统计。
以下是具体操作过程,不需要可以跳过。
- 从根节点0出发,先到达1.发现结点1有询问
LCA(1,6)
,查询发现结点6还未遍历,忽略。
-
从1出发,到达2,记录发现结点2有询问
LCA(2,10)
,查询结点10还未遍历,忽略。 -
2没有子节点了,标记2已经访问,回溯到父节点1,记录fa[2]=1。
- 从1出发,到达3,记录fa[3]=1,结点3没有询问,继续遍历。
- 从3出发,分别到达4,5,结点4的询问点没有出现,忽略并回溯;结点5没有询问,回溯。记录fa[4]=fa[5]=3
-
从3出发,到达6,记录fa[6]=3,分别处理结点6的两个询问。
-
对于LCA(6,4),只需要记录结点4的祖先(通过get),fa[4]=3->fa[3]=3->停止,所以LCA(6,4)的答案即为3,记录在对应答案编号下。
-
对于LCA(6,1),同样求结点1的祖先,因为1还没有回溯,所以fa[1]=1->停止,记录1到LCA(6,1)下。
-
结点6,3,1回溯,记录fa[6]=3,fa[3]=1,fa[1]=0,回溯到结点0.
-
从0出发,遍历结点7,遍历结点8,结点8回溯,记录fa[8]=7......
-
其余同理。
好了,下拉到这里就可以了
下面是全过程展示,黄边为已经确定父子结点关系的边。
相信你已经理解了为什么要把一个询问分成两次记录下来,因为这两次里面虽然只会有一次有效,但是我们不能知道哪一次是有效的,所以就都记录下来,方便判断。
至于原理...首先我们在LCA(u,v)的询问中,答案肯定是v的祖先,而深度最小的那个,即v的祖先中第一个还没有回溯的结点f。u一定是由f往下递归得到的。可以多看几次GIF演示来想想是为什么。
参考代码如下:
#include<bits/stdc++.h>
using namespace std;
struct edge
{
int to,next;
}e[1000010];
int head[1000010],cnt;
int fa[500010];
bool vis[500010];
int ans[500010];
struct node
{
int v,id;
};
vector<node> ask[500010];
void add(int u,int v)
{
e[++cnt]=(edge){v,head[u]};
head[u]=cnt;
}
int get(int x)
{
return fa[x]==x?x:fa[x]=get(fa[x]);
}
void dfs(int u,int f)
{
for(int i=head[u];i;i=e[i].next)
if(e[i].to!=f)dfs(e[i].to,u),fa[e[i].to]=u;
int len=ask[u].size();
for(int i=0;i<len;i++)
if(vis[ask[u][i].v])ans[ask[u][i].id]=get(ask[u][i].v);
vis[u]=1;
}
int main()
{
int n,m,s;
cin>>n>>m>>s;
for(int i=1;i<=500000;i++)
fa[i]=i;
for(int i=1;i<n;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=m;i++)
{
int u,v;
scanf("%d%d",&u,&v);
ask[u].push_back((node){v,i});
ask[v].push_back((node){u,i});
}
dfs(s,0);
for(int i=1;i<=m;i++)
printf("%d\n",ans[i]);
return 0;
}